• 从零开始搭建一个 React + Mobx + React Router 脚手架


    本文详细介绍了如何从零开始搭建一个 React 开发的脚手架,包含如何添加 Redux 以及 React Router 的环境。

    本文代码地址:react-mobx-starter

    建议将代码拉下来之后,配合本文一起查看,效果更佳。

    代码下载命令:

    git clone -b example https://github.com/beichensky/react-mobx-starter.git
    复制代码

    最近将脚手架中的 babel 配置更新到了 7.0.0 版本,所以部分地方作出了修改。目前脚手架中的各类库的版本如下:

    • node:10.15.3
    • npm:6.4.1
    • webpack: 4.29.6
    • webpack-cli: 3.3.0
    • webpack-dev-server: 3.1.4
    • @babel/core: 7.0.0
    • react: 16.8.6
    • mobx: 5.9.4
    • react-router-dom: 5.0.0

    如果是从低版本升级到 7.0.0 版本,官方提供了一个新的命令可以直接帮助我们对项目进行更新:

    npx babel-upgrade --write --install
    复制代码

    更多关于 babel-upgrade 的介绍可以参考官方说明


    一、前情提要

    本文的 Demo 分为两个环境,一个是开发环境,另一个是生产环境。

    • 开发环境中讲述的是如何配置出一个更好的、更方便的开发环境;

    • 而生产环境中讲述的是如何配置出一个更优化、更小版本的生产环境。

    之前我也就 Webpack 的使用写了几篇文章,本文也是在 Webpack 的基础上进行开发,也是在之前的代码上进行的扩展。

    建议:对于 Webpack 还不了解的朋友,可以先看一下 从零开始搭建一个 Webpack 开发环境配置(附 Demo)使用 Webpack 进行生产环境配置(附 Demo) 这两篇文章,可以更好的入手本文。

    虽然本文是在之前文章上进行的扩展,但本文还是会详细的介绍每一步的配置。


    二、创建项目结构

    新建文件夹,命名为:react-mobx-starter

    mkdir react-mobx-starter 
    复制代码

    初始化 package.json 文件

    cd react-mobx-starter
    
    # 直接生成默认的 package.json 文件
    npm init -y
    复制代码

    创建 src 目录,用来存放我们编写的代码 创建 public 目录,用来存放公共的文件 创建 webpack 目录,用来存放 webpack 配置文件

    mkdir src
    
    mkdir public
    
    mkdir webpack
    复制代码

    在 src 目录下 新建 pages 文件夹,用来存放书写的页面组件 新建 components 文件夹,用来存放公共组件 新建 utils 文件夹,用来存放常用的工具类

    cd src
    
    mkdir pages
    
    mkdir components
    
    mkdir utils
    复制代码

    public 目录下新建 index.html 文件 在 src 目录下新建 index.js 文件 在 webpack 目录下创建 webpack.config.dev.jswebpack.config.prod.js

    • webpack.config.dev.js 用来编写 webpack 开发环境配置
    • webpack.config.prod.js 用来编写 webpack 生产环境配置

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>React + Mobx 全家桶脚手架</title>
    </head>
    <body>
        <div id="root"></div>
    </body>
    </html>
    复制代码

    index.js

    function createElement() {
        const ele = document.createElement('div');
        ele.innerHTML = 'Hello, React';
        const root = document.querySelector('#root');
        root.appendChild(ele);
    }
    
    createElement();
    复制代码

    webpack.config.dev.jswebpack.config.prod.js 此时还没有书写内容,我们之后会详细的进行讲述。

    我们看一下此时的项目结构,之后就可以进行 webpack 的配置了。

    react-mobx-starter
      ├─ public/
        └─ index.html
      ├─ src/
        ├─ components/
        ├─ pages/
        ├─ utils/
        └─ index.js
      ├─ webpack/
        ├─ webpack.config.dev.js
        └─webpack.config.prod.js
      ├─ package.json
    复制代码

    三、React 开发环境配置

    在 package.json 文件中添加一个执行脚本,用来执行 webpack 命令:

    {
        ...,
        "scripts": {
            "start": "webpack --config webpack/webpack.config.dev.js"
        },
        ...
    }
    复制代码

    安装 webpack 相关插件

    安装 webpack 和 webpack-cli

    npm install webpack webpack-cli --save-dev
    复制代码

    入口和出口

    使用 webpack 进行项目配置的时候,必须要有入口和出口,作为模块引入和项目输出。

    webpack.config.dev.js

    const path = require('path');
    
    const appSrc = path.resolve(__dirname, '../src');
    const appDist = path.resolve(__dirname, '../dist');
    const appPublic = path.resolve(__dirname, '../public');
    const appIndex = path.resolve(appSrc, 'index.js');
    
    module.exports = {
        entry: appIndex,
        output: {
            filename: 'public/js/[name].[hash:8].js',
            path: appDist,
            publicPath: '/'
        }
    }
    复制代码

    添加 html-webpack-plugin 插件

    执行 npm run start 脚本,可以看到 dist/public/js 目录下多了一个 js 文件,但是这个是由 hash 值命名的的,我们每次都手动引入到 index.html 文件里面过于麻烦,所以可以引入 html-webpack-plugin 插件。

    html-webpack-plugin 插件有两个作用

    • 可以将 public 目录下的文件夹拷贝到 dist 输出文件夹下
    • 可以自动将 dist 下的 js 文件引入到 html 文件中

    安装 html-webpack-plugin 插件

    npm install html-webpack-plugin --save-dev
    复制代码

    使用 html-webpack-plugin 插件

    webpack.config.dev.js

    
    const path = require('path');
    + const HTMLWebpackPlugin = require('html-webpack-plugin');
    
    const appSrc = path.resolve(__dirname, '../src');
    const appDist = path.resolve(__dirname, '../dist');
    const appPublic = path.resolve(__dirname, '../public');
    const appIndex = path.resolve(appSrc, 'index.js');
    + const appHtml = path.resolve(appPublic, 'index.html');
    
    module.exports = {
        entry: appIndex,
        output: {
            filename: 'public/js/[name].[hash:8].js',
            path: appDist,
            publicPath: '/'
        },
    +    plugins: [
    +        new HTMLWebpackPlugin({
    +            template: appHtml,
    +            filename: 'index.html'
    +        })
    +    ]
    }
    复制代码

    设置开发模式

    webpack 配置中的 mode 属性,可以设置为 'development' 和 'production',我们目前是进行开发环境配置,所以可以设置为 'development'

    webpack.config.dev.js

    ...
    module.exports = {
    +    mode: 'development',
        ...
    }
    复制代码

    设置 devtool

    为了方便在项目出错时,迅速定位到错误位置,可以设置 devtool,生成资源映射,我们这里使用 inline-source-map,更多选择可以在这里查看区别。

    webpack.config.dev.js

    ...
    module.exports = {
        mode: 'development',
    +    devtool: 'inline-source-map',
        ...
    }
    复制代码

    使用 webpack-dev-server 启动项目服务

    安装 webpack-dev-server

    npm install webpack-dev-server --save-dev
    复制代码

    配置 webpack-dev-server

    webpack.config.dev.js

    ...
    module.exports = {
        mode: 'development',
        devtool: 'inline-source-map',
    +    devServer: {
    +        contentBase: appPublic,
    +        hot: true,
    +        host: 'localhost',
    +        port: 8000,
    +        historyApiFallback: true,
    +        // 是否将错误展示在浏览器蒙层
    +        overlay: true,
    +        inline: true,
    +        // 打印信息
    +        stats: 'errors-only',
    +        // 设置代理
    +        proxy: {
    +            '/api': {
    +                changeOrigin: true,
    +                target: 'https://easy-mock.com/mock/5c2dc9665cfaa5209116fa40/example',
    +                pathRewrite: {
    +                    '^/api/': '/'
    +                }
    +            }
    +        }
    +    },
        ...
    }
    复制代码

    修改一下 package.json 文件中的 start 脚本:

    {
        ...,
        "scripts": {
            "start": "webpack-dev-server --config webpack/webpack.config.dev.js"
        },
        ...
    }
    复制代码

    使用 friendly-errors-webpack-plugin 插件

    friendly-errors-webpack-plugin 插件可以在命令行展示更有好的提示功能。

    安装 friendly-errors-webpack-plugin

    npm install friendly-errors-webpack-plugin --save-dev
    复制代码

    使用 friendly-errors-webpack-plugin

    webpack.config.dev.js

    
    const path = require('path');
    const HTMLWebpackPlugin = require('html-webpack-plugin');
    + const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
    
    ...
    
    module.exports = {
        ...
        plugins: [
            new HTMLWebpackPlugin({
                template: appHtml,
                filename: 'index.html'
            }),
    +        new FriendlyErrorsWebpackPlugin(),
        ]
    }
    复制代码

    启用热加载

    webpack.config.dev.js

    const path = require('path');
    const HTMLWebpackPlugin = require('html-webpack-plugin');
    const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
    + const webpack = require('webpack');
    
    ...
    
    module.exports = {
        ...
        plugins: [
            ...
            new FriendlyErrorsWebpackPlugin(),
    +        new webpack.HotModuleReplacementPlugin()
        ]
    }
    复制代码

    运行项目,测试配置成果

    执行 npm run start 命令,命令行提示成功后,在浏览器打开 http://localhost:8000,可以看到 Hello React,说明基本的Webpack 配置已经成功了。

    配置 babel

    我们现在 index.js 里面的代码量比较少,所以没有问题。但是我如果想在里面使用一些 ES6 的语法或者是还未被标准定义的 JS 特性,那么我们就需要使用 babel 来进行转换了。下面我们来配置一下 babel

    安装 babel 相关插件

    npm install @babel/core babel-loader --save-dev
    复制代码

    使用 babel-loader

    设置 cacheDirectory 属性,指定的目录将用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程。

    webpack.config.dev.js

    ...
    module.exports = {
        ...
        plugins: [ ... ],
        module: {
            rules: [
                {
                    test: /.(js|jsx)$/,
                    loader: 'babel-loader?cacheDirectory',
                    include: [ appSrc ],
                    exclude: /node_modules/
                }
            ]
        }
    }
    复制代码

    在项目根目录下新建 babel.config.js 文件

    babel.config.js 文件是运行时控制文件,在项目编译的时候会自动读取 babel.config.js 文件中的 babel 配置。

    使用 babel 相关 presets

    安装相关插件:

    • @babel/preset-env:可以在项目中使用所有 ECMAScript 标准里的最新特性。
    • @babel/preset-react:可以在项目中使用 react 语法。
    npm install babel-preset-env babel-preset-react --save-dev
    复制代码

    配置 babel.config.js 文件:

    module.exports = (api) => {
        api.cache(true);
    
        return {
            presets: [
                "@babel/preset-env",
                "@babel/preset-react"
            ]
        }
    }
    复制代码

    使用 babel 相关 plugins

    babel 升级到 7.0.0 版本之后, @babel/preset-stage-0 被废弃,用到的插件需要自己进行安装。 如果是从低版本升级到 7.0.0 版本,官方提供了一个新的命令可以直接帮助我们对项目进行更新:

    npx babel-upgrade --write --install
    复制代码

    更多关于 babel-upgrade 的介绍可以参考官方说明

    安装相关插件:

    • @babel/plugin-proposal-decorators:可以在项目中使用装饰器语法。
    • @babel/plugin-proposal-class-properties:可以在项目中使用新的 class 属性语法。
    • @babel/plugin-transform-runtime:使用此插件可以直接使用 babel-runtime 中的代码对 js 文件进行转换,避免代码冗余。
    • @babel/runtime-corejs2:配合 babel-plugin-transform-runtime 插件成对使用
    • @babel/plugin-syntax-dynamic-import:可以在项目中使用 import() 这种语法
    • @babel/plugin-proposal-export-namespace-from:可以使用 export * 这种命名空间的方式导出模块
    • @babel/plugin-proposal-throw-expressions:可以使用异常抛出表达式
    • @babel/plugin-proposal-logical-assignment-operators:可以使用逻辑赋值运算符
    • @babel/plugin-proposal-optional-chaining:可以使用可选链的方式访问深层嵌套的属性或者函数 ?.
    • @babel/plugin-proposal-pipeline-operator:可以使用管道运算符 |>
    • @babel/plugin-proposal-nullish-coalescing-operator:可以使用空值合并语法 ??
    • @babel/plugin-proposal-do-expressions:可以使用 do 表达式(可以认为是三元运算符的复杂版本)
    • @babel/plugin-proposal-function-bind:可以使用功能绑定语法 obj::func
    npm install @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties @babel/plugin-transform-runtime @babel/runtime-corejs2 @babel/plugin-syntax-dynamic-import @babel/plugin-proposal-export-namespace-from @babel/plugin-proposal-throw-expressions @babel/plugin-proposal-logical-assignment-operators @babel/plugin-proposal-optional-chaining @babel/plugin-proposal-pipeline-operator @babel/plugin-proposal-nullish-coalescing-operator @babel/plugin-proposal-do-expressions @babel/plugin-proposal-function-bind --save-dev
    复制代码

    配置 babel.config.js 文件:

    module.exports = (api) => {
        api.cache(true);
    
        return {
            presets: [
                "@babel/preset-env",
                "@babel/preset-react"
            ],
            plugins: [
                [
                    "@babel/plugin-proposal-decorators",
                    {
                        "legacy": true
                    }
                ],
                [
                    "@babel/plugin-transform-runtime",
                    {
                        "corejs": 2
                    }
                ],
                [
                    "@babel/plugin-proposal-class-properties", 
                    { 
                        "loose": true
                    }
                ],
                "@babel/plugin-syntax-dynamic-import",
                // 可以使用 export * 这种命名空间的方式导出模块
                "@babel/plugin-proposal-export-namespace-from",
                // 可以使用异常抛出表达式,
                "@babel/plugin-proposal-throw-expressions",
                // 默认导出
                "@babel/plugin-proposal-export-default-from",
                // 可以使用逻辑赋值运算符
                "@babel/plugin-proposal-logical-assignment-operators",
                // 可以使用可选链的方式访问深层嵌套的属性或者函数 ?.
                "@babel/plugin-proposal-optional-chaining",
                // 可以使用管道运算符 |> 
                [
                    "@babel/plugin-proposal-pipeline-operator",
                    {
                        "proposal": "minimal"
                    }
                ],
                // 可以使用空值合并语法 ??
                "@babel/plugin-proposal-nullish-coalescing-operator",
                // 可以使用 do 表达式(可以认为是三元运算符的复杂版本)
                "@babel/plugin-proposal-do-expressions",
                // 可以使用功能绑定语法 obj::func
                "@babel/plugin-proposal-function-bind"
            ]
        }
    }
    
    复制代码

    这里需要注意 @babel/plugin-proposal-decorators 插件的放置顺序,最好放在第一位,否则可能会出现某些注解失效的问题。

    至此,babel 相关的基本配置完成了。之后我们就可以在项目中肆意使用各种新的 JS 特性了。

    添加 css 相关 loader

    js 文件相关的 babel-loader 配置好了,但是有时候我们想在项目中为元素添加一些样式,而 webpack 中认为一切都是模块,所以我们这时候也需要别的 loader 来解析一波样式代码了。

    安装相关插件:

    • css-loader:处理 css 文件中的 url() 等。
    • style-loader:将 css 插入到页面的 style 标签。
    • less-loader:是将 less 文件编译成 css
    • postcss-loader:可以集成很多插件,用来操作 css。我们这里使用它集成 autoprefixer 来自动添加前缀。
    npm install css-loader style-loader less less-loader postcss-loader autoprefixer --save-dev
    复制代码

    配置样式相关 loader

    • 由于 React 无法直接使用类似 Vuescope 这种局部作用变量,所以我们可以使用 webpack 提供的 CSS Module。 2、由于等会儿会使用 antd,所以引入 antd 时需要开启 lessjavascript 选项,所以要将 less-loader 中的属性 javascriptEnabled 设置为 true

    在 webpack.config.dev.js 中配置:

    ...
    const autoprefixer = require('autoprefixer');
    
    module.exports = {
        ...,
        plugins: [...],
        module: {
            rules: [
                ...,
                {
                    test: /.(css|less)$/,
                    exclude: /node_modules/,
                    use: [{
                            loader: 'style-loader'
                        },
                        {
                            loader: 'css-loader',
                            options: {
                                sourceMap: true,
                                modules: true,
                                localIdentName: '[local].[hash:8]'
                            }
                        },
                        {
                            loader: 'postcss-loader',
                            options: {
                                plugins: () => [autoprefixer()]
                            }
                        },
                        {
                            loader: 'less-loader',
                            options: {
                                javascriptEnabled: true
                            }
                        }
                    ]
                },
                {
                    test: /.(css|less)$/,
                    include: /node_modules/,
                    use: [{
                            loader: 'style-loader'
                        },
                        {
                            loader: 'css-loader',
                            options: {}
                        },
                        {
                            loader: 'postcss-loader',
                            options: {
                                plugins: () => [autoprefixer()]
                            }
                        },
                        {
                            loader: 'less-loader',
                            options: {
                                javascriptEnabled: true
                            }
                        }
                    ]
                },
            ]
        }
    }
    复制代码

    添加其他模块解析 loader 配置

    安装相关插件:

    npm install file-loader csv-loader xml-loader html-loader markdown-loader --save-dev
    复制代码

    在 webpack.config.dev.js 中配置:

    ...
    
    module.exports = {
        ...,
        plugins: [...],
        module: {
            rules: [
                ...,
                // 解析图片资源
                {
                    test: /.(png|svg|jpg|gif)$/,
                    use: [
                        'file-loader'
                    ]
                },
                // 解析 字体
                {
                    test: /.(woff|woff2|eot|ttf|otf)$/,
                    use: [
                        'file-loader'
                    ]
                },
                // 解析数据资源
                {
                    test: /.(csv|tsv)$/,
                    use: [
                        'csv-loader'
                    ]
                },
                // 解析数据资源
                {
                    test: /.xml$/,
                    use: [
                        'xml-loader'
                    ]
                },
                // 解析 MakeDown 文件
                {
                    test: /.md$/,
                    use: [
                        'html-loader',
                        'markdown-loader'
                    ]
                }
            ]
        }
    }
    复制代码

    额外的 webpack 配置优化

    添加 resolve allias 属性,设置别名

    在项目开发过程中,随着项目越来越大, 文件层级越来越深,引入文件的时候可能会需要一层一层的找路径,就会比较繁琐,我们可以使用 resolve 中的 alias 属性为一些常用的文件夹设置别名

    webpack.config.dev.js

    ··· 
    
    module.exports = {
        ...,
        plugins: [...],
        module: {...},
    +    resolve: {
    +        alias: {
    +            src: appSrc,
    +            utils: path.resolve(__dirname, '../src/utils'),
    +            pages: path.resolve(__dirname, '../src/pages'),
    +            components: path.resolve(__dirname, '../src/components')
    +        }
    +    }
    }
    复制代码

    添加 resolve.modules 属性,指明第三方模块存放位置

    我们知道,一般进行模块搜索时,会从当前目录下的 node_modules 一直搜索到磁盘根目录下的 node_modules。所以为了减少搜索步骤,我们可以设置 resolve.modules 属性强制只从项目的 node_modules 中查找模块。

    webpack.config.dev.js

    ··· 
    
    module.exports = {
        ...,
        plugins: [...],
        module: {...},
        resolve: {
            ...,
    +        modules: [path.resolve(__dirname, '../node_modules')],
        }
    }
    复制代码

    安装 React、MObx 以及 React Router 相关插件

    npm install react react-dom prop-types mobx mobx-react react-router-dom --save
    复制代码

    引入 antd

    按照 antd 官网的说明,直接在 babel.config.js 文件中添加配置,之后即可在项目中正常使用了。

    安装 antd 相关插件:

    npm install antd moment --save
    复制代码

    安装 babel-plugin-import 对组件进行按需加载:

    npm install babel-plugin-import --save-dev
    复制代码

    babel.config.js 文件中添加 antd 配置:

    module.exports = (api) => {
        api.cache(true);
        
        return {
            presets: [
                ...
            ],
            plugins: [
                ...,
        +        [
        +            "import",
        +            {
        +                "libraryName": "antd",
        +                "style": true
        +            }
        +        ],
                ...
            ]
        }
    复制代码

    四、进行 React 开发

    基本上需要的插件目前都已经引入了,是时候进行开发了。

    修改根目录下的 index.js 文件

    index.js

    import React from 'react';
    import ReactDom from 'react-dom';
    import { Provider } from 'mobx-react'
    import { LocaleProvider } from 'antd';
    import { HashRouter } from 'react-router-dom';
    import zh_CN from 'antd/lib/locale-provider/zh_CN';
    import 'moment/locale/zh-cn';
    
    import GlobalModel from './GlobalModel';
    // import App from './App';
    
    const globalModel = new GlobalModel();
    
    const App = () => {
        return <div>开发环境配置完成</div>
    }
    
    ReactDom.render(
        <Provider globalModel={ globalModel }>
            <LocaleProvider locale={zh_CN}>
                <HashRouter>
                    <App />
                </HashRouter>
            </LocaleProvider>
        </Provider>,
        document.querySelector('#root')
    );
    复制代码

    运行 npm run start 命令,在浏览器打开 http://localhost:8000/,就能够看到 开发环境配置完成 正常显示。

    此时说明我们各种插件、库都已经引入完成,可以正常使用了。

    使用 React Route 进行页面间路由跳转

    在 src 目录下新建 App.js 文件:

    App.js

    import React from 'react';
    import { Switch, Route } from 'react-router-dom';
    
    import Home from 'pages/home';
    import Settings from 'pages/settings';
    import Display from 'pages/display';
    import NotFound from 'pages/exception'
    
    import styles from './App.less';
    
    
    export default (props) => {
        return (
            <div className={ styles.app }>
                <Switch>
                    <Route path='/settings' component={ Settings } />
                    <Route path='/display' component={ Display } />
                    <Route exact path='/' component={ Home } />
                    <Route component={ NotFound } />
                </Switch>
            </div>
        )
    }
    复制代码

    src 目录下创建 App.less 文件,编写 App 组件样式

    App.less

    .app {
        padding: 60px;
    }
    复制代码

    在 pages 目录下编写 Home、Settings、Display、NotFound 组件

    • Home 组件是根路由组件,用来跳转到 Setting 界面和 Display 界面
    • Settings 组件演示了如何获取和修改 mobx 的全局 Model
    • Display 组件演示了如何使用 mobx 进行同步和异步的数据处理
    • NotFound 组件在匹配不到正确路由时展示

    Home、Settings、Display 相关的代码我就不贴了,占的篇幅较长,大家需要的话可以去我的 Github 上看一下或者下载下来也可以。比较方便。地址:Github

    再修改一下 index.js 文件

    index.js

    import React from 'react';
    import ReactDom from 'react-dom';
    import { Provider } from 'mobx-react'
    import { LocaleProvider } from 'antd';
    import { HashRouter } from 'react-router-dom';
    import zh_CN from 'antd/lib/locale-provider/zh_CN';
    import 'moment/locale/zh-cn';
    
    import GlobalModel from './GlobalModel';
    import App from './App';
    
    const globalModel = new GlobalModel();
    
    ReactDom.render(
        <Provider globalModel={ globalModel }>
            <LocaleProvider locale={zh_CN}>
                <HashRouter>
                    <App />
                </HashRouter>
            </LocaleProvider>
        </Provider>,
        document.querySelector('#root')
    );
    复制代码

    可以看到这里有一个 GlobalModel 存放全局通用数据的 Model,里面的逻辑比较简单,我们稍微看一下。

    GlobalModel.js

    import { observable, action } from 'mobx';
    
    export default class GlobalModel {
        @observable username = '小明';
    
        @action
        changeUserName = (name) => {
            this.username = name;
        }
    
    }
    复制代码

    添加 fetch 工具类进行网络请求

    由于我们在 Display 组件中需要进行网络请求的异步操作,所以我们这里引入 fetch 进行网络请求。

    安装 fetch 相关插件:

    npm install whatwg-fetch qs --save
    复制代码

    编写网络请求工具类

    utils 目录下创建 request.js 文件。

    utils/request.js

    import 'whatwg-fetch';
    import { stringify } from 'qs';
    
    /**
     * 使用 Get 方式进行网络请求
     * @param {*} url 
     * @param {*} data 
     */
    export const get = (url, data) => {
        const newUrl = url + '?' + stringify(data) + (stringify(data) === '' ? '' : '&') +'_random=' + Date.now();
        return fetch(newUrl, {
                cache: 'no-cache',
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json; charset=utf-8'
                },
                method: 'GET',
            })
            .then(response => response.json());
    }
    
    /**
     * 进行 Post 方式进行网络请求
     * @param {*} url 
     * @param {*} data 
     */
    export const post = (url, data) => {
        return fetch(url, {
            body: JSON.stringify(data), 
            cache: 'no-cache',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json; charset=utf-8'
            },
            method: 'POST',
        })
        .then(response => response.json()) // parses response to JSON
    }
    复制代码

    至此,简单的 React 框架搭建完成了

    执行 npm run start,编译成功后,可以看到界面长这样。

    Home 界面:

    Home 界面

    Settings 界面:

    Settings 界面

    Display 界面:

    Display 界面

    NotFound 界面:

    NotFound 界面

    五、进行 React 项目打包配置

    先在 package.json 文件中新增一个执行脚本:

    {
        ...,
        "scripts": {
            "start": "webpack-dev-server --config webpack/webpack.config.dev.js",
            "build": "webpack --config webpack/webpack.config.prod.js"
        },
        ...
    }
    复制代码

    配置 webpack.config.prod.js 文件

    其中大部分的 moduleplugin 还有 resolve 都与开发环境的一致。所以我们就以 webpack.config.dev.js 文件中的配置为基础进行说明。

    • 将 mode 属性值修改为:'production'
    • 将 devtool 属性值修改为:'hidden-source-map'
    • 删除 devServer 属性所有的配置。
    • 删除使用的热加载插件:·webpack.HotModuleReplacementPlugin`,

    添加 optimization 属性进行代码压缩

    安装相关插件:

    npm install uglifyjs-webpack-plugin optimize-css-assets-webpack-plugin --save-dev
    复制代码

    添加代码压缩配置:

    webpack.config.prod.js

    ...;
    const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
    const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
    ...;
    
    module.exports = {
        mode: 'production',
        devtool: 'hidden-source-map',
        entry: ...,
        output: {...},
        plugins: [...],
        module: {...},
        optimization: {
            // 打包压缩js/css文件
            minimizer: [
                new UglifyJsPlugin({
                    uglifyOptions: {
                        compress: {
                            // 在UglifyJs删除没有用到的代码时不输出警告
                            warnings: false,
                            // 删除所有的 `console` 语句,可以兼容ie浏览器
                            drop_console: true,
                            // 内嵌定义了但是只用到一次的变量
                            collapse_vars: true,
                            // 提取出出现多次但是没有定义成变量去引用的静态值
                            reduce_vars: true,
                        },
                        output: {
                            // 最紧凑的输出
                            beautify: false,
                            // 删除所有的注释
                            comments: false,
                        }
                    }
                }),
                new OptimizeCSSAssetsPlugin({})
            ],
            splitChunks: {
                cacheGroups: {
                    styles: {
                        name: 'styles',
                        test: /.(css|less)/,
                        chunks: 'all',
                        enforce: true,
                        reuseExistingChunk: true // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。
                    },
                    commons: {
                        name: 'commons',
                        chunks: 'initial',
                        minChunks: 2,
                        reuseExistingChunk: true
                    },
                    vendors: {
                        name: 'vendors',
                        test: /[\/]node_modules[\/]/,
                        priority: -10,
                        reuseExistingChunk: true
                    }
                }
            },
            runtimeChunk: true
        },
        resolve: {...}
    }
    复制代码

    使用 mini-css-extract-plugin 插件提取 CSS 代码

    安装相关插件:

    npm install mini-css-extract-plugin --save-dev
    复制代码

    配置 mini-css-extract-plugin 插件:

    • plugins 属性中引入
    • modulerules 中使用的 style-loader 替换为 MiniCssExtractPlugin.loader

    webpack.config.prod.js

    ...
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    module.exports = {
        ...,
        plugins: [
            ...,
            new MiniCssExtractPlugin({
                filename: 'public/styles/[name].[contenthash:8].css',
                chunkFilename: 'public/styles/[name].[contenthash:8].chunk.css'
            })
        ],
        modules: {
            rules: [
                ...,
                {
                    test: /.(css|less)$/,
                    exclude: /node_modules/,
                    use: [
                        {
    -                        loader: 'style-loader'
    +                        loader: MiniCssExtractPlugin.loader
                        },
                        ...
                    ]
                },
                {
                    test: /.(css|less)$/,
                    exclude: /node_modules/,
                    use: [
                        {
    -                        loader: 'style-loader'
    +                        loader: MiniCssExtractPlugin.loader
                        },
                        ...
                    ]
                },
                ...
            ]
        },
        ...
    }
    复制代码

    使用 DefinePlugin 插件定义当前为生产环境

    webpack.config.prod.js

    ...
    module.exports = {
        ...,
    
        plugins: [
            ...,
            new webpack.DefinePlugin({
                // 定义 NODE_ENV 环境变量为 production
                'process.env': {
                    NODE_ENV: JSON.stringify('production')
                }
            })
        ],
        ...
    }
    复制代码

    使用 clean-webpack-plugin 清理 dist 目录

    打包的过程中,由于部分文件名使用的是 hash 值,会导致每次文件不同,因而在 dist 中生成一些多余的文件。所以我们可以在每次打包之前清理一下 dist 目录。

    安装插件:

    npm install clean-webpack-plugin --save-dev
    复制代码

    使用插件:

    webpack.config.prod.js

    ...,
    
    module.exports = {
        ...,
        plugins: [
            ...,
            new CleanWebpackPlugin()
        ],
        ...
    }
    复制代码

    添加其他优化配置

    • 添加 stats 配置过滤打包时出现的一些统计信息。
    • 添加 performance 配置关闭性能提示

    webpack.config.prod.js

    module.exports = {
        ...,
        stats: {
            modules: false,
            children: false,
            chunks: false,
            chunkModules: false
        },
        performance: {
            hints: false
        }
    }
    复制代码

    进行项目的打包发布

    打包项目

    运行 npm run build 指令,控制台打包完成之后,根目录下多出了 dist 文件夹。

    使用 Nginx 发布项目

    我这里是用的是 Nginx 作为服务器,发布在本地。

    Nginx 下载地址:nginx.org/en/download…

    下载完成之后,解压完成。打开 Nginx 目录,可以找到一个 conf 文件夹,找到其中的 nginx.conf 文件,修改器中的配置:

    Nginx 配置

    将图中标注的 html 更换为 dist

    然后我们就可以放心的将打包生成的 dist 文件夹直接放到 Nginx 的安装目录下了。(此时 dist 目录与刚才的 conf 目录应该是同级的)。

    启动 Nginx 服务:

    start nginx
    复制代码

    打开浏览器,输入 http://127.0.0.1 或者 http://localhost 即可看到我们的项目已经正常的跑起来了。

    Nginx 其他命令:

    # 停止 Nginx 服务
    nginx -s stop
    
    # 重启 Nginx 服务
    nginx -s reload
    
    # 退出 nginx
    nginx -s quit
    复制代码

    更多 Ngnix 相关请参考:Nginx官方文档

    注意:需要在 Nginx 安装目下执行 nginx 相关命令!


    六、使用 webpack-merge 引入webpack 公共配置

    观察 webpack.config.dev.jswebpack.config.prod.js 文件,可以发现有大量的代码和配置出现了重复。所以我们可以编写一个 webpack.common.js 文件,将共有的配置放入其中,然后使用 webpack-merge 插件分别引入到 webpack.config.dev.jswebpack.config.prod.js 文件中使用。

    插件安装:

    npm install webpack-merge --save-dev
    复制代码

    使用:

    + const merge = require('webpack-merge');
    + const common = require('./webpack.common.js');
    
    + module.exports = merge(common, {
    +   mode: 'production',
    +   ...
    + });
    复制代码

    这里就展示了一下用法,由于篇幅太长,三个文件中具体的配置代码我就不贴了, 大家可以到 我的 GitHub 上查看一下使用 webpack-merge 后的配置文件。


    七、本文源码地址

    react-mobx-starter

    欢迎Star,谢谢各位!

    文章及代码中如有问题,欢迎指正,谢谢!


    八、参考文档

    webpack 官网

    从零开始搭建一个 Webpack 开发环境配置(附 Demo)

    使用 Webpack 进行生产环境配置(附 Demo)


    作者:暖生
    链接:https://juejin.im/post/5caee4266fb9a0688144ec68
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    VS2008 编译出错 fatal error C1859: unexpected precompiled header error, simply rerunning the compiler might fix this problem
    解析XML出错,无法创建DOMDocument对象
    strncpy, strncpy_s
    Mongodb: Sort operation used more than the maximum 33554432 bytes of RAM
    node-cache
    【Boost】boost::string_algo详解2——find相关函数
    Compiler Error: Function call with parameters that may be unsafe
    fopen和fopen_s用法的比较
    fopen_s遇到的一个问题
    Data type conversion in MongoDB
  • 原文地址:https://www.cnblogs.com/yuluoxingkong/p/13083497.html
Copyright © 2020-2023  润新知