webpack 从0开始搭建 react(优化篇)

前言

上一篇文章讲的如何使用 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或更低版本,请使用[email protected]。该[email protected]版本及以上支持的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')
        })
    ]
}

六、环境区分

到目前为止 我们 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 源码实现相关的文章,你的点赞就是我的动力,请求一个点赞+关注。感谢。

发布了2 篇原创文章 · 获赞 0 · 访问量 20

猜你喜欢

转载自blog.csdn.net/weixin_45394033/article/details/105252846
今日推荐