美文网首页
使用 webpack4 从0开始搭建 react 项目(优化篇)

使用 webpack4 从0开始搭建 react 项目(优化篇)

作者: shanyq | 来源:发表于2020-04-01 18:42 被阅读0次

    前言

    上一篇文章讲的如何使用 webpack 项目地址: 搭建一个简易的项目使用 webpack4 从0开始搭建 react 项目。这一篇将基于上篇文章项目继续做项目优化、环境区分。

    一、分离 css 样式,生成 css 文件

    项目中我们一般使用 less/sass 来写样式,这里的话以 less 为例,首先我们安装

    
    npm i less less-loader -D
    
    

    使用 less-loader

    
    {
    
        test: /\.(css|less)$/,
    
        use: ['style-loader', 'css-loader', 'less-loader']
    
    }
    
    

    在 入口文件 中引入 less 文件

    
    // index.less
    
    body {
    
        background: red;
    
        div {
    
            display: flex;
    
            color: #fff;
    
        }
    
    
    
        span {
    
            color: blue;
    
        }
    
    }
    
    

    postcss-loader 帮你将现代 CSS 语法转换成大多数浏览器都能理解的东西,根据你的目标浏览器或运行时环境来确定你需要的 polyfills,基于 cssdb 实现。 这里的话

    使用 postcss-loader 来做浏览器适配,autoprefixer 会自动增加浏览器前缀。

    现在样式还是通过 js 生成 style标签插入到页面当中,可以使用 MiniCssExtractPlugin 来生成 css 样式

    
    npm i mini-css-extract-plugin postcss-loader autoprefixer -D
    
    

    在 webpakc.config.js 中配置

    
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    module: {
    
        rules: [
    
            {
    
                test: /\.(css|less)$/,
    
                use: [
    
                    {
    
                        loader: MiniCssExtractPlugin.loader,
    
                        options: {
    
                            //  您可以在此处指定publicPath
    
                            //  默认情况下,它在webpackOptions.output中使用publicPath
    
                            publicPath: '../'
    
                        },
    
                        // 这里会直接到 src 文件下找 less/css 文件进行编译,这里是项目优化的一个小技巧
    
                        include: path.resolve(__dirname, './src')
    
                    }, 'css-loader', 'postcss-loader', 'less-loader']
    
            },
    
        ]
    
    }
    
    plugins: [
    
        new MiniCssExtractPlugin({
    
            //  选项类似于webpackOptions.output中的相同选项
    
            //  所有选项都是可选的
    
            filename: "[name].css",
    
            chunkFilename: "[id].css",
    
            ignoreOrder :false ,//  启用以删除有关顺序冲突的警告 
    
        })
    
    ]
    
    

    根目录下新建 postcss.config.js 来配置 postcss

    
    module.exports = {
    
        plugins: [
    
            require('autoprefixer')({
    
                overrideBrowserslist: ['last 2 versions', '>1%']
    
            })
    
        ]
    
    }
    
    

    现在只需要将 css 压缩并且开启 tree shanking (摇树)功能,Css就配置完成了。

    安装

    
    // tree shanking 需要的插件
    
    npm i glob-all purifycss-webpack purify-css -D
    
    // cssnano 将你的 CSS 文件做 多方面的的优化,以确保最终生成的文件 对生产环境来说体积是最小的。
    
    // web️对于webpack v3或更低版本,请使用optimize-css-assets-webpack-plugin@3.2.0。该optimize-css-assets-webpack-plugin@4.0.0版本及以上支持的WebPack V4。
    
    npm i optimize-css-assets-webpack-plugin cssnano -D
    
    

    在 webpack.config.js 中使用

    
    // 压缩 Css 文件
    
    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
    
    // 用于处理多路径文件,使用purifycss的时候要用到glob.sync方法。
    
    const glob = require('glob-all')
    
    // Css tree shanking 摇树
    
    const purifyCss = require('purifycss-webpack')
    
    {
    
        plugins: [
    
            // 压缩css文件
    
            new OptimizeCssAssetsWebpackPlugin({
    
                cssProcessor: require('cssnano'),
    
                cssProcessorPluginOptions: {
    
                    // 去掉注释
    
                    preset: ["default", { discardComments: { removeAll: true } }]
    
                }
    
            }),
    
            new purifyCss({
    
                paths: glob.sync([
    
                    path.resolve(__dirname, './*html'),
    
                    path.resolve(__dirname, './src/*js')
    
                ])
    
            })
    
        ]
    
    }
    
    

    二、Optimization

    通过webpack打包提取公共代码

    
    optimization: {
    
        // js 开启 tree shanking
    
        usedExports: true,
    
        splitChunks: {
    
            chunks: "all", // 代码分隔 公共代码分离出来
    
            name: true,
    
            cacheGroups: {
    
                // [\\/] 解决系统之间的兼容
    
                react: {
    
                    test: /[\\/]react|react-dom[\\/]/,
    
                    name: 'react'
    
                },
    
                lodash: {
    
                    test: /[\\/]lodash[\\/]/,
    
                    name: 'lodash'
    
                }
    
            }
    
        }
    
    },
    
    

    minimize

    如果mode是production类型,minimize的默认值是true,执行默认压缩,

    minimizer

    允许你使用第三方的压缩插件,可以在optimization.minimizer的数组列表中进行配置

    splitChunks

    
    splitChunks: {
    
        chunks: "all", // 默认作用于异步chunk,值为all/initial/async/function(chunk),值为function时第一个参数为遍历所有入口chunk时的chunk模块,chunk._modules为chunk所有依赖的模块,通过chunk的名字和所有依赖模块的resource可以自由配置,会抽取所有满足条件chunk的公有模块,以及模块的所有依赖模块,包括css
    
        minSize: 30000,  //表示在压缩前的最小模块大小,默认值是30kb
    
        minChunks: 1,  // 表示被引用次数,默认为1;
    
        maxAsyncRequests: 5,  //所有异步请求不得超过5个
    
        maxInitialRequests: 3,  //初始话并行请求不得超过3个
    
        automaticNameDelimiter:'~',//名称分隔符,默认是~
    
        name: true,  //打包后的名称,默认是chunk的名字通过分隔符(默认是~)分隔
    
        cacheGroups: { //设置缓存组用来抽取满足不同规则的chunk,下面以生成common为例
    
            common: {
    
                name: 'common',  //抽取的chunk的名字
    
                chunks(chunk) { //同外层的参数配置,覆盖外层的chunks,以chunk为维度进行抽取
    
                },
    
                test(module, chunks) {  //可以为字符串,正则表达式,函数,以module为维度进行抽取,只要是满足条件的module都会被抽取到该common的chunk中,为函数时第一个参数是遍历到的每一个模块,第二个参数是每一个引用到该模块的chunks数组。自己尝试过程中发现不能提取出css,待进一步验证。
    
                },
    
                priority: 10,  //优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中
    
                minChunks: 2,  //最少被几个chunk引用
    
                reuseExistingChunk: true,//  如果该chunk中引用了已经被抽取的chunk,直接引用该chunk,不会重复打包代码
    
                enforce: true  // 如果cacheGroup中没有设置minSize,则据此判断是否使用上层的minSize,true:则使用0,false:使用上层minSize
    
            }
    
        }
    
    }
    
    

    更多配置请参考 webpack-optimizationsplitchunks

    三、resolve

    
    resolve: {
    
        // 规定在那里寻找第三方模块
    
        modules: [path.resolve(__dirname, './node_modules')],
    
        // 别名 我们可以通过别名的方式快速定位到引用包的/方法的路劲,优化打包和运行本地服务
    
        alias: {
    
            react: path.resolve(__dirname, './node_modules/react/umd/react.production.min.js'),
    
            'react-dom': path.resolve(__dirname, './node_modules/react-dom/umd/react-dom.production.min.js'),
    
            '@': path.resolve(__dirname, './src')
    
        },
    
        // 自动补齐后缀名,这个列表会让webpack一级一级寻找,尽量少配置
    
        extensions: ['.js', '.jsx']
    
    },
    
    

    更多配置请参考 webpack-resolve

    四、js 开启 tree shanking

    webpack 内置了 js 的摇树功能,在生产环境下,Tree-shaking会进行自动删除的操作

    如果通过ES6的import引用的方式就会把没有用到的代码给删除掉。

    在 package.json 中配置

    
    {
    
        "sideEffects": false
    
    }
    
    

    打包后会发现引用的 less 文件也会被过滤。因为webpack 人为 less 文件引用但是未被使用。

    修改一下 sideEffects 的值

    
    {
    
        "sideEffects": [
    
            "*.css",
    
            "*.less"
    
        ]
    
    }
    
    

    五、DllPlugin

    引用官方描述:

    这个插件是在一个额外的独立的 webpack 设置中创建一个只有 dll 的 bundle(dll-only-bundle)。 这个插件会生成一个名为 manifest.json 的文件,这个文件是用来让 DLLReferencePlugin 映射到相关的依赖上去的。
    

    简单说就是讲公共依赖缓存起来,不用每次运行都打包一遍

    根目录下新建 webpack.dll.config.js

    
    const path = require('path')
    
    const { DllPlugin } = require('webpack')
    
    module.exports = {
    
        entry: {
    
            react: ['react', 'react-dom']
    
        },
    
        mode: 'development',
    
        output: {
    
            path: path.resolve(__dirname, './dll'),
    
            filename: "[name].dll.js",
    
            library: 'react'
    
        },
    
        plugins: [
    
            new DllPlugin({
    
                // 生成一个 manifest.json 文件,并指定位置
    
                path: path.join(__dirname, './dll', '[name]-manifest.json'),
    
                name: 'react' // name 要和 labray 名称一致
    
            })
    
        ]
    
    }
    
    

    package.json 中新增命令

    
    scripts: {
    
        "dev:dll": "webpack --config ./webpack.dll.config.js",
    
    }
    
    

    运行 npm run dev:dll 后会在根目录下生成一个 dll 文件

    在 webpack.config.js 中使用

    
    const webpack = require('webpack')
    
    {
    
        plugins: [
    
            new webpack.DllReferencePlugin({
    
                manifest: path.resolve(__dirname, './dll/react-manifest.json')
    
            }),
    
        ]
    
    }
    
    

    到目前为止还需要在 index.html 中手动引入生成的 react.dll.js 文件才算配置完成,这里的话我们借助插件 AddAssetHtmlWebpackPlugin 可以帮你自动添加 js 到 html 中

    安装

    
    npm i add-asset-html-webpack-plugin -D
    
    

    webpack.config.js 中使用

    
    const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
    
    {
    
        plugins: [
    
            new AddAssetHtmlWebpackPlugin({
    
                filepath: path.resolve(__dirname, './dll/react.dll.js')
    
            })
    
        ]
    
    }
    
    

    六、环境区分

    image

    到目前为止 我们 webpack.config.js 文件已经非常庞大了。很多生产环境和开发环境的配置也是冲突的

    这里就要区分环境了,这里我们需要安装几个包帮助我们来合并 webpack 配置对象,通过命令传参 等

    
    // 合并 webpack 配置对象
    
    npm i webpack-merge -D
    
    // 在执行命令的时候传参
    
    npm i cross-env -D
    
    

    根目录下新建 webpack.dev.config.js / webpack.pro.config.js,将 webpack.config.js 改名为 webpack.base.config.js

    webpack.base.config.js

    
    // webpack 默认配置
    
    const path = require('path');
    
    const htmlWebpackPlugin = require('html-webpack-plugin');
    
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    module.exports = {
    
        entry: path.resolve(__dirname, './src/react.js'),
    
        output: {
    
            path: path.resolve(__dirname, './dist'),
    
            filename: 'main_[hash:8].js'
    
        },
    
        module: {
    
            rules: [
    
                {
    
                    test: /\.css|less$/,
    
                    use: [
    
                        {
    
                            loader: MiniCssExtractPlugin.loader,
    
                            options: {
    
                                //  您可以在此处指定publicPath
    
                                //  默认情况下,它在webpackOptions.output中使用publicPath
    
                                publicPath: '../'
    
                            }
    
                        }, 'css-loader', 'postcss-loader', 'less-loader'
    
                    ],
    
                    // 这里会直接到 src 文件下找 less/css 文件进行编译,这里是项目优化的一个小技巧
    
                    include: path.resolve(__dirname, './src')
    
                },
    
                {
    
                    test: /\.(png|jpg|gif)$/,
    
                    use: [
    
                        {
    
                            loader: 'file-loader',
    
                            options: {},
    
                        },
    
                    ],
    
                },
    
                {
    
                    test: /\.js$/,
    
                    loader: 'babel-loader'
    
                },
    
            ]
    
        },
    
        plugins: [
    
            // 复制一个 html 并将最后打包好的资源在 html 中引入
    
            new htmlWebpackPlugin({
    
                // 页面title 需要搭配 ejs 使用
    
                title: "webpack-react",
    
                // html 模板路径
    
                template: "./index.html",
    
                // 输出文件名称
    
                filename: "index.html",
    
                minify: {
    
                    // 压缩HTML⽂件
    
                    removeComments: true, // 移除HTML中的注释
    
                    collapseWhitespace: true, // 删除空⽩符与换⾏符
    
                    minifyCSS: true // 压缩内联css
    
                }
    
            }),
    
            // 每次部署时清空 dist 目录
    
            new CleanWebpackPlugin(),
    
            new MiniCssExtractPlugin({
    
                filename: "css/[name]_[contenthash:6].css",
    
            })
    
        ],
    
        resolve: {
    
            // 规定在那里寻找第三方模块
    
            modules: [path.resolve(__dirname, './node_modules')],
    
            // 别名
    
            alias: {
    
                react: path.resolve(__dirname, './node_modules/react/umd/react.production.min.js'),
    
                'react-dom': path.resolve(__dirname, './node_modules/react-dom/umd/react-dom.production.min.js'),
    
                '@': path.resolve(__dirname, './src')
    
            },
    
            // 自动补齐后缀名
    
            extensions: ['.js', '.jsx']
    
        },
    
    }
    
    

    修改 webpack.dev.config.js

    
    // webpack 默认配置
    
    const path = require('path');
    
    const webpack = require("webpack");
    
    // 引入js到 html 文件中
    
    const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
    
    const merge = require('webpack-merge');
    
    const webpackBase = require('./webpack.base.config');
    
    // merge 用法和 Object.assign 类似
    
    module.exports = merge(webpackBase, {
    
        mode: 'development',
    
        plugins: [
    
            // 启用模块热替换(HMR - Hot Module Replacement)
    
            new webpack.HotModuleReplacementPlugin(),
    
            new webpack.DllReferencePlugin({
    
                manifest: path.resolve(__dirname, './dll/react-manifest.json')
    
            }),
    
            new AddAssetHtmlWebpackPlugin({
    
                filepath: path.resolve(__dirname, './dll/react.dll.js')
    
            })
    
        ],
    
        devtool: 'cheap-module-eval-source-map',
    
        // // 启动项目
    
        devServer: {
    
            contentBase: './dist',
    
            open: true,
    
            port: 8081,
    
            hot: true,
    
            hotOnly: true
    
        },
    
    })
    
    

    webpack.pro.config.js

    
    // webpack 默认配置
    
    const path = require('path');
    
    // 压缩 Css 文件
    
    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
    
    // 用于处理多路径文件,使用purifycss的时候要用到glob.sync方法。
    
    const glob = require('glob-all')
    
    // Css tree shanking 摇树
    
    const purifyCssWebpack = require('purifycss-webpack')
    
    module.exports = {
    
        mode: 'production',
    
        plugins: [
    
            // 压缩css文件
    
            new OptimizeCssAssetsWebpackPlugin({
    
                cssProcessor: require('cssnano'),
    
                cssProcessorPluginOptions: {
    
                    // 去掉注释
    
                    preset: ["default", { discardComments: { removeAll: true } }]
    
                }
    
            }),
    
            new purifyCssWebpack({
    
                paths: glob.sync([
    
                    path.resolve(__dirname, './src/*html'),
    
                    path.resolve(__dirname, './src/*js')
    
                ])
    
            }),
    
        ],
    
        optimization: {
    
            // js 开启 tree shanking
    
            usedExports: true,
    
            splitChunks: {
    
                chunks: "all", // 代码分隔 公共代码分离出来
    
                name: true,
    
                cacheGroups: {
    
                    react: {
    
                        test: /[\\/]react|react-dom[\\/]/,
    
                        name: 'react'
    
                    },
    
                    lodash: {
    
                        test: /[\\/]lodash[\\/]/,
    
                        name: 'lodash'
    
                    }
    
                }
    
            }
    
        }
    
    }
    
    

    修改 package.json 文件

    
    "scripts": {
    
        "dev": "webpack ",
    
        "dev:dll": "webpack --config ./webpack.dll.config.js",
    
        "server": "webpack-dev-server --config ./webpack.dev.config.js",
    
        // 这里通过 cross-env 传了一个 NODE_ENV 变量 可以通过 process.env.NODE_ENV 获取变量的值
    
        "build": "cross-env NODE_ENV=production webpack --config ./webpack.pro.config.js"
    
    },
    
    

    七、happypack

    webpack 在 node 环境下运行也是单线程,所有操作都要等待上一步完成。这里可以借助 happypack 的多线程的功能,给 webpack 开个挂,实现多进程打包

    由于HappyPack 对file-loader、url-loader支持的不友好,所以不建议对该loader使用。
    

    安装

    
    npm i happypack -D
    
    

    webpack.base.config.js 中配置一下

    
    const Happypack = require('happypack');
    
    //构造出一个共享进程池,在进程池中包含4个子进程
    
    const happyThreadPool = Happypack.ThreadPool({
    
        size: 4
    
    })
    
    module: {
    
        rules: [
    
            {
    
                test: /\.js$/,
    
                use: 'Happypack/loader?id=happypackJs',
    
                include: path.resolve(__dirname, './src')
    
            }
    
        ]
    
    },
    
    plugins: [
    
        new Happypack({
    
            // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
    
            id: 'happypackJs',
    
            // 如何处理 .js 文件,用法和 Loader 配置中一样
    
            use: ['babel-loader'],
    
            //使用共享进程池中的自进程去处理任务
    
            threadPool: happyThreadPool,
    
            //是否允许happypack输出日志,默认true
    
            verbose: true
    
        }),
    
    ]
    
    

    总结

    到目前为止 webpack 篇章就算结束了,后续的话我将来写一下 Vue 源码实现相关的文章,你的点赞就是我的动力,请求一个点赞+关注。感谢。

    相关文章

      网友评论

          本文标题:使用 webpack4 从0开始搭建 react 项目(优化篇)

          本文链接:https://www.haomeiwen.com/subject/pboauhtx.html