zoukankan      html  css  js  c++  java
  • 从零开始搭建一个 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
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    Leetcode 191.位1的个数 By Python
    反向传播的推导
    Leetcode 268.缺失数字 By Python
    Leetcode 326.3的幂 By Python
    Leetcode 28.实现strStr() By Python
    Leetcode 7.反转整数 By Python
    Leetcode 125.验证回文串 By Python
    Leetcode 1.两数之和 By Python
    Hdoj 1008.Elevator 题解
    TZOJ 车辆拥挤相互往里走
  • 原文地址:https://www.cnblogs.com/yuluoxingkong/p/13083497.html
Copyright © 2011-2022 走看看