个人博客

A web front-end programmer's personal blog.

webpack 使用总结

webpack 使用总结

基础配置

    const path = require('path');
    module.exports = {
        entry: './app/index.js',
        context: path.resolve(__dirname),
        output: {
            path: path.resolve(__dirname, 'dist'),
            filename: 'bundle.[hash:8].js',
            publicPath: 'https://www.example.com'
        },
        module: {
            rules: []
        },
        resolve: {},
        devtool: 'source-map',
        plugins: [],
        externals: {} //页面之间引入的库,不进行webpack打包
    };

html-webpack-plugin

多入口

    entry: {
        home: './app/home.js',
        other: './app/other.js'
    }
    ...
    new HtmlWebpackPlugin({
        template: './index.html',
        filename: 'home.html',
        hash: true,
        minify: {
            removeAttributeQuotes: true,
            collapseWhitespace: true
        }
        chunks: ['home','other']
    }),
    new HtmlWebpackPlugin({
        template: './index.html',
        filename: 'other.html',
        chunks: ['other']
    })

样式处理和css抽离

    rules: [
        {
            test: /.css$/,
            use: [
                'style-loader,
                'css-loader',
                {
                    loader: 'postcss-loader',
                    options: {
                        plugins: [require('autoprefixer')()]
                    }
                }
                'sass-loader',
            ] 
        }
    ]

抽离css(mini-css-extract-plugin)、js 及文件分类

    const TerserJSPlugin = require('terser-webpack-plugin'); //压缩js
    const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); // 压缩css
    const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 抽离css
    ...
    entry: './app/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'js/bundle.[hash:8].js'
    },
    ...
    optimization: {
        minimizer: [
            new TerserJSPlugin({}),
            new OptimizeCSSAssetsPlugin({})
        ],
    },
    ...
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader',
                ] 
            },
            {
                test: /\.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 1024*1,
                        outputPath: '/imgs'
                    }
                }
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'css/index.css'
        }),
    ]

postcss.config.js

    module.exports = {
        plugins: [require('autoprefixer')]
    }

es6语法转换

    module: {
        rules: [
            {
                test: /.js$/,
                use: [
                    'babel-loader'
                ],
                exclude: /node_modules/,
                include: path.resolve(__dirname,'src')
            }
        ]
    }

.babelrc

    {
        "presets": ["@babel/preset-env"],
        "plugins": [
            ["@babel/plugin-proposal-decorators", { "legacy": true }],
            ["@babel/plugin-proposal-class-properties", { "loose" : true }],
            ["@babel/plugin-transform-runtime"]
        ]
    }

@babel/plugin-transform-runtime 开发依赖 转换 generator等 @babel/runtime 生产依赖 runtime可以将babel转换的公共helper方法抽离公用 但不能转换实例上的方法像 str.includes(), 需要 @bable/polyfill

js校验 eslint

    rules: [
        {
            test: /.js$/,
            use: [
                'babel-loader'
            ],
            exclude: /node_modules/,
            include: path.resolve(__dirname,'src')
        },
        {
            test: /.js$/,
            use: {
                loader: 'eslint-loader',
                options:{
                    enforce: 'pre'  // enforce 强制前/后 'pre/post'
                }
            },
            exclude: /node_modules/,
            include: path.resolve(__dirname,'src')
        }
    ]

.eslintrc.json

    {
        "rules": {
          "semi": ["error", "always"],
          "quotes": ["error", "double"]
        }
        ...官网配置
    }

全局暴露及全局变量、环境变量

webpack 内置插件 ProvidePlugin 在每个模块中注入模块 DefinePlugin定义变量

    plugin: [
        new webpack.ProvidePlugin({
            $: 'jquery'
            _: 'loash'
        }),
        new webpack.DefinePlugin({
            DEV: JSON.stringify('development')
        })
    ]

图片处理

    {
        test: /\.(png|jpg|jpeg|gif)$/,
        use: {
            loader: 'html-withimg-loader',
        }
    },
    {
        test: /\.(png|jpg|jpeg|gif)$/,
        use: {
            loader: 'url-loader',
            options: {
                limit: 1024*1,
                outputPath: '/imgs',
                publicPath: 'http://images.example.cn'
            }
        }
    }

source-map 源码映射

    {
        module :{
            rules: [...]
        },
        devtool: 'source-map'
    }
  • source-map: 会单独生成一个 sourcemap文件,出错会显示行和列
  • eval-source-map: 不会单独产生一个文件,可以显示行和列
  • cheap-module-source-map: 不会产生列 但会生成一个单独文件 产生后可以保留
  • cheap-module-eval-source-map: 不会产生文件,集成在打包后的文件中,只点位行不定位列

常用插件

  • cleanWebpackPlugin
  • copyWebpackPlugin
  • bannerPlugin(内置插件,添加文件前注释)
    const cleanWebpackPlugin = require('clean-webpack-plugin');
    const copyWebpackPlugin = require('copy-webpack-plugin');
    const webpack = require('webpack')
    plugins: [
        new cleanWebpackPlugin(['./dist']),
        new copyWebpackPlugin([
            {form: './document', to: './dist'}
        ]),
        new webpack.BannerPlugin('author: blue')
    ]

devServer 及跨域代理

    devServer: {
        proxy: {
            '/api': {
                target: 'http://xxx.xxx',
                pathRewrite: {
                    '/api': '/'
                }
            }
        }
    }

devServer 其他配置

    devServer: {
        before(app){ // app 即 express 的 app 此处可以代表mock数据
            app.get('/user', (req,res) => {
                res.json({status:true,data:[1,2,3]})
            })
        }
    }

另外如果服务端为 node服务,可以 服务端启动webpack 避免跨域问题 使用 webpack-dev-middleware

    const app = express();
    const config = require('./webpack.config.js')
    const middle = require('webpack-dev-middleware')
    const compiler = webpack(config);
    app.use(middle(compiler))
    ....
    app.listen(3000)

resolve配置

    resolve: {
        extensions: ['.js','.json','.jsx','.css'], //省略扩展名
        modules: [path.resolve('node_modules')], //只在被目录下的 node_modules中查找模块
        alias: { // 别名
            vue: 'vue/dist/vue.rutime.js',
            bootstrap: 'bootstrap/dist/css/bootstrap.css'
        },
        mainFields: ['style','main'], //优先加载 package.json 中的key中的路径
        mainFiles: [], //package.json中入口文件路径 默认 main: index.js
    }

wepack优化 noParse

    module: {
        noParse: /jquery/, //不解析 jquery 模块中的依赖(减少解析时间)
        rules: [
            {test: /.js$/}
        ]
    }

webpack优化 exclude include

    module:{
        rules: [
            {
                test: /.js$/,
                exclude: /node_modules/,
                include: path.reslove(__dirname,'src')
            }
        ]
    }

webpack优化 webpack.IngorePlugin

    plugins: [
        // 忽略掉在 node_modules/moment模块中 正则匹配./locale/ 引入的相关模块
        new webpack.IgnorePlugin(/\.\/locale/,/moment/),
    ]

webpack优化 webpack.DllPlugin

内置模块 将一些库单独打包 如 vue,react,react-dom,jquery,loadsh等,单独打包到一个文件,只打包业务代码,减少打包时间

    //webpack.libs.js 公共库单独抽离打包
    const path = require('path')
    const webpack = require('webpack');

    module.exports = {
        mode: 'production',
        entry: {
            common: ['moment','vue','loadsh','jquery','react','react-dom']
        },
        output: {
            filename: '_dll_[name].js',
            path: path.resolve(__dirname,'dist'),
            library: '_dll_[name]',
            libraryTarget: 'umd' //取值有 umd commonjs var 等
        },
        plugins: [
            new webpack.IgnorePlugin(/\.\/locale/,/moment/),
            new webpack.DllPlugin({
                name: '_dll_[name]',
                path: path.resolve(__dirname,'dist','manifest.json')
            })
        ]
    }


    //webpack.config.js 项目打包配置
    {
        ...
        plugins: [
            new webpack.DllReferencePlugin({
                manifest: path.resolve(__dirname,'dist','manifest.json')
            }),
        ]
    }

另外需要在 html模板文件中手动添加最后打包的公共库

     <script src="_dll_common.js"></script>

webpack优化 happypack 多线程打包

    const Happypack = require('happypack')

    rules: [
        {
            test: /.js$/,
            exclude: /node_modules/,
            include: path.reslove(__dirname,'src'),
            use: 'Happypack/loader?id=aaa'
        },
        {
            test: /.css$/,
            exclude: /node_modules/,
            include: path.reslove(__dirname,'src'),
            use: 'Happypack/loader?id=bbb'
        }
    ],
    plugins: [
        new Happypack({
            id: 'aaa',
            use: [
                loader: 'babel-loader',
                options: {
                    ...
                }
            ]
        }),
        new Happypack({
            id: 'bbb',
            use: ['style-loader','css-loader']
        })
    ]

webpack自带优化 tree-shaking, scope hosting

tree-shaking 只加载用的的代码 scope-hosting 作用域提升,会自动省略一些可以简化的代码

webpack 抽取公共代码(多入口文件代码公共部分抽离)

注意与 dllplugin的区别,针对多入口提取公共代码

    {
        ...
        optimization: {
            splitChunks: { //代码分割
                cacheGroups: {  //抽离缓存组
                    common: { //业务中公共模块抽离 名字为common
                        chunks: 'initial', // 三个值 initial 打包开始时 async 异步引入 all 所有的
                        minSize: 0, //最小大小
                        minChunks: 2 //最小引入次数
                    },
                    vendor: { //第三方库抽离 名字为 vendor
                        priority: 1, //权重提升,避免与上面的 common 影响
                        test: /node_modules/, //通常第三方库位置
                        chunks: 'initial',
                        minSize: 0,
                        minChunks: 2
                    }
                }
            },
        }
    }

webpack 懒加载

依赖插件 @babel/plugin-syntax-dynamic-import

    .babelrc
    plugins: [
        ...
        "@babel/plugin-syntax-dynamic-import"
    ]

实例代码

    button.addEventListener('click',()=>{
        import('./async-a').then(res => {
            console.log(res);
        })
    });

webpack 热更新

    devServer: {
        ...
        hot: true
    },
    plugins: [
        ...
        new webpack.HotModuleReplacementPlugin(),
    ]

入口文件添加下面代码

    if (module.hot) {
        module.hot.accept()
    }
Newer >>