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

2020-04-01  本文已影响0人  shanyq

前言

上一篇文章讲的如何使用 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 源码实现相关的文章,你的点赞就是我的动力,请求一个点赞+关注。感谢。

上一篇 下一篇

猜你喜欢

热点阅读