使用 webpack4 从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或更低版本,请使用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 源码实现相关的文章,你的点赞就是我的动力,请求一个点赞+关注。感谢。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容