美文网首页
webpack从配置到跑路v2

webpack从配置到跑路v2

作者: hellomyshadow | 来源:发表于2020-05-23 22:20 被阅读0次

    入口

    打包默认的入口文件:src/index.js
    配置入口的属性:entry

    //webpack.config.js
    module.exports = {
        entry: './src/index.js'
    }
    

    entry的属性值可以是一个字符串、数组或对象

    • 字符串:配置单个文件的入口
    • 数组:配置多个入口文件,一起注入到同一个 chunk 文件中
      entry: [
          './src/polyfills.js',
          './src/index.js'
      ]
      
      其中,polyfills.js中可能只是简单地引入一些polyfill,如babel-polyfill、whatwg-fetch,需要在最前面被引入。而且,在没有配置出口的情况下,它们都会打包进dist/main.js
    • 对象:可以将不同文件分隔开进行打包,在后面多页面时会详细讲解。

    出口

    默认出口文件:dist/main.js,配置编译文件的输出属性为 output

    const path = require('path');
    module.exports = {
        entry: './src/index.js',
        output: {
            path: path.resolve(__dirname, 'dist'),    // 必须是绝对路径
            filename: 'bundle.js',  // 输出一个名为 `bundle.js` 的文件
            publicPath: '/'    //通常是CDN地址
        }
    }
    
    • filename
      filename 指定具体名称时,只会输出一个文件(chunk);当有多个 chunk 要输出时,就需要借助模版和变量了
      entry: {  // 多入口
          app: './src/app.js',
          search: './src/search.js'
      },
      output: {  // 多出口
          path: path.resolve(__dirname, 'dist'),
          filename: '[name].js'   // 使用 chunk 的原始名称
      }
      --> 输出:dist/app.js, dist/search.js
      
      考虑到CDN的缓存问题,通常会为编译后的文件名加上hash
      filename: 'bundle.[hash].js',
      
    • publicPath:引用资源的发布路径
      publicPath: 'http://www.xxx.com/'
      
      // 编译构建之后 index.html 与 CSS
      <img src=http://www.xxx.com/assets/preview.jpg>
      background-image: url(http://www.xxx.com/assets/bg.png);
      <script src=http://www.xxx.com/bundle.3a954523c44ca9020fd3.js></script>
      

    清理dist目录

    每次编译并不会删除旧文件,如果文件名中带有hash,那每次打包都会生成一个新的文件;插件clean-webpack-plugin 可以在每次打包前清理输出目录中的文件。

       npm i -D clean-webpack-plugin
    
       // webpack.config.js
       const { CleanWebpackPlugin } = require('clean-webpack-plugin');
       plugins: [
           new CleanWebpackPlugin(),
       ]
    

    CleanWebpackPlugin可以接受一个配置对象,如果不传,则去找output.path,默认是dist目录;
    配置对象可以做很多事,比如不希望dll目录每次都清理:

       new CleanWebpackPlugin({
            // 不清理 dll 目录下的文件
            cleanOnceBeforeBuildPatterns: ['**/*', '!dll', '!dll/**']
       })
    

    更多配置项

    静态文件的拷贝

    有时候,我们需要使用已有的JS文件、CSS文件、图片等静态资源,但不需要webpack编译,比如在index.html中引入public目录下的js/css/image,编译后肯定找不到了,而手动拷贝public目录到输出目录必然又容易忘记。
    插件copy-webpack-plugin 支持将单个文件或整个目录拷贝到构建(输出)目录。

    npm i copy-webpack-plugin -D
    
    // webpack.config.js
    const CopyWebpackPlugin = require('copy-webpack-plugin');
    plugins: [
        new CopyWebpackPlugin([
            {
                // 拷贝 public 目录下的所有js文件 到 dist/js 目录中
                from: 'public/js/*.js',
                to: path.resolve(__dirname, 'dist', 'js'),
                flatten: true
            },
            {
                // 把 /src/assets 拷贝到 /dist/assets
                from: path.join(__dirname, 'src/assets'),
                to: path.join(__dirname, 'dist/assets')
            },
        ])
    ]
    

    参数 flatten 设置为 true 时,只会拷贝文件,而不会把文件夹路径都拷贝上。
    CopyWebpackPlugin还可以配置忽略哪些文件:ignore

    new CopyWebpackPlugin([
        {
            from: 'public/js/*.js',
            to: path.resolve(__dirname, 'dist', 'js'),
            flatten: true
        }
    ], {
        ignore: ['other.js']   // 忽略 `other.js` 文件
    })
    

    ProvidePlugin

    ProvidePluginwebpack的内置插件,用于设置全局变量,在项目中不需要import/require就能使用。

    new webpack.ProvidePlugin({
      identifier1: 'module1',
      identifier2: ['module2', 'property2']
    });
    

    默认查找路径是当前目录 ./**node_modules,当然也可以直接指定全路径。
    React、jquery、lodash这样的库,可能在多个文件中使用,把它们配置为全局变量,就不需要在每个文件中手动导入了。

    const webpack = require('webpack');
    module.exports = {
        //...
        plugins: [
            new webpack.ProvidePlugin({
                React: 'react',
                Component: ['react', 'Component'],
                Vue: ['vue/dist/vue.esm.js', 'default'],
                $: 'jquery',   // npm i jquery -S
                _map: ['lodash', 'map']  // 把lodash中的map()配置为全局方法
            })
        ]
    }
    

    配置之后,在项目中可以随便使用$、_map了,且在编写React组件时,也不需要import React/Component了。
    ReactVue的到处方式不同,所以配置方式有所区别。vue.esm.js中是通过export default导出的,而React使用的是moudle.exports导出的。
    如果项目中启用了eslint,还需要修改其配置问家:

    {
        "globals": {
            "React": true,
            "Vue": true,
            //....
        }
    }
    

    如果把第三方库手动下载添加到项目目录下,比如/static/js/jquery.min.js
    那么可以先给它配置别名,然后再配置为全局变量:

    module.exports = {
        //...
        resolve: {
            alias: {  // 配置JS库的路径的别名
                jQuery: path.resolve(__dirname, 'static/js/jquery.min.js')
            }
        },
        plugins: [
            new webpack.ProvidePlugin({
                $: 'jQuery',   // 优先从 alias 别名对应的路径去查找
            })
        ]
    }
    

    提取抽离CSS

    很多时候我们都会把抽离CSS,单独打包成一个或多个文件,这样既可以提高加载速度,又有利于缓存。
    插件mini-css-extract-plugin(新版)extract-text-webpack-plugin(旧版)相比:

    • 异步加载、不会重复编译(性能更好)
    • 更容易使用、只适用于CSS

    mini-css-extract-plugin的安装与配置

        npm i mini-css-extract-plugin -D
    
        // webpack.config.js
        const MiniCssExtractPlugin = require('mini-css-extract-plugin');
        module.exports = {
            module: {
                rules: [
                    {
                        test: /\.(le|c)ss$/,
                        use: [MiniCssExtractPlugin.loader, //替换之前的 style-loader
                            'css-loader', {
                                loader: 'postcss-loader',
                                options: {
                                    plugins: function () {   // [require('autoprefixer')]
                                        return [
                                            require('autoprefixer')()
                                        ]
                                    }
                                }
                            }, 'less-loader']
                    }
                ]
            },
            plugins: [
                new MiniCssExtractPlugin({
                    filename: 'css/[name].css'  // 将css文件放在单独目录下
                    // publicPath:'../',
                    // chunkFilename: 'css/[id].[hash].css'
                })
            ]
        }
    
    • src/index.js 中引入CSSimport '../public/base.less'
    • npm run build构建打包生成:dist/css/main.css,默认原名称为main

    注意:如果output.publicPath配置的是 ./ 这种相对路径,那么如果将css文件放在单独目录下,记得配置MiniCssExtractPluginpublicPath

    开发模式下,只有第一次修改CSS才会刷新页面,需要为MiniCssExtractPlugin.loader配置参数:

    const ENV = process.env.NODE_ENV
    
    use: [
        {
            loader: MiniCssExtractPlugin.loader,
            options: {
                hmr: ENV === 'development',
                reloadAll: true,
            }
        }, 
        // ...
    ]
    

    更多配置项 查看mini-css-extract-plugin

    CSS压缩

    抽离CSS默认不会被压缩,插件optimize-css-assets-webpack-plugin用于压缩CSS

        npm i optimize-css-assets-webpack-plugin -D
    
        // webpack.config.js
        const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
        plugins: [
            new OptimizeCssAssetsPlugin()
        ],
    

    OptimizeCssPlugin还可以传入一个配置对象:

    new OptimizeCssAssetsPlugin({
        assetNameRegExg: /\.css$/g,
        cssProcessor: require('cssnano'),
        cssProcessorPluginOptions: {
            preset: ['default', {discardComments: {removeAll: true}}]
        },
        canPrint: true
    })
    
    • assetNameRegExg:正则表达式,用于匹配需要优化或压缩的资源名,默认值/\.css$/g
    • cssProcessor:用于压缩和优化CSS的处理器,默认cssnano
    • cssProcessorPluginOptionscssProcessor的插件选项,默认值{}
    • canPrint:表示插件能够在console中打印信息,默认值true
    • discardComments:去除注释

    按需加载JS

    很多时候并不需要一次性加载所有js文件,而应该在不同阶段去加载所需要的代码。
    webpack内置了强大的代码分割功能,可以实现按需加载--> import()
    比如,在点击了某个按钮之后,才需要使用对应js文件中的代码

    dom.onclick = function() {
        import('./handle').then(fn => fn.default());
    }
    

    import()语法需要 @babel/plugin-syntax-dynamic-import 插件的支持,但预设@babel/preset-env中已经包含了该插件,所以无需单独安装和配置。
    npm run build构建时发现,webpack会生成一个新的chunk文件。

    webpack遇到 import(xxxx) 时:

    • xxxx 为入口生成一个新的 chunk
    • 当代码执行到 import() 时,才会加载该 chunk 文件。

    热更新

    当前修改js文件会刷新整个页面,热更新会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。
    热更新:即模块热替换,HMR,主要通过以下几种方式来显著加快开发速度

    • 保留在完全重新加载页面时丢失的应用程序状态;
    • 只更新变化的内容,以节省宝贵的开发时间;
    • 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。

    首先配置 devServer.hottrue,然后使用webpack的内置插件HotModuleReplacementPlugin

        modules.exports = {
            devServer: {
                hot: true,  // 开启热更新
                // hotOnly: true,
                contentBase: './dist',
                // ...
            },
            plugins: [
                new webpack.NamedModulesPlugin(),
                new webpack.HotModuleReplacementPlugin()
            ]
        }
    
    • hotOnly 表示只有热更新,不会自动刷新页面;
    • NamedModulesPlugin 开启HMR时使用该插件会显示模块的相对路径

    虽然修改CSS会自动热更新,但修改JS代码仍然是整个页面刷新,还需要在JS文件中告诉webpack接收更新的模块

        // 修改入口文件 src/index.js
        if(module && module.hot) {
            module.hot.accept()
        }
    

    多页应用打包

    我们的应用不一定是一个单页应用,而是一个多页应用,那么webpack如何进行打包呢。
    插件html-webpack-plugin可以配置多个,每一个HtmlWebpackPlugin对应一个页面,其filename属性控制打包后的页面名称,默认值是index.html,所以对于多页应用,一定不能省略filename

    // webpack.config.js
    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    module.exports = {
        entry: {
            index: './src/index.js',
            login: './src/login.js'
        },
        output: {
            path: path.resolve(__dirname, 'dist'),
            filename: '[name].[hash:6].js'
        },
        //...
        plugins: [
            new HtmlWebpackPlugin({
                template: './public/index.html',
                filename: 'index.html' // 打包后的文件名
            }),
            new HtmlWebpackPlugin({
                template: './public/login.html',
                filename: 'login.html'
            }),
        ]
    }
    

    查看打包后的index.htmllogin.html发现,entry中配置的入口文件会在每个页面中都引入一次。而我们想要的是index.html只引入./src/index.jslogin.html只引入./src/login.js
    HtmlWebpackPlugin提供了一个chunks属性,接受一个数组,用于指定将哪些入口js文件引入到页面中。

    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html',
            chunks: ['index']  // 数组元素为 entry中的键名
        }),
        new HtmlWebpackPlugin({
            template: './public/login.html',
            filename: 'login.html',
            chunks: ['login']
        }),
    ]
    

    还有一个和chunks相对立的属性excludeChunks,指定哪些文件不想引入到页面中。

    resolve

    resolve 配置 webpack 如何查找模块所对应的文件。
    webpack内置JavaScript模块化语法解析功能,默认会采用模块化标准里约定好的规则去查找,但也可以根据自己的需要修改默认规则。

    • modules
      resolve.modules 配置webpack去哪些目录下查找第三方模块,默认只会去node_modules下查找。
      如果项目中某个目录下的模块经常被导入,但又不希望写很长的路径,那么就可以通过配置modules来简化。
          //webpack.config.js
          module.exports = {
              //....
              resolve: {
                  modules: ['./src/components', 'node_modules'] //从左到右依次查找
              }
          }
      
      配置之后,import Dialog from 'dialog'就会先去./src/components下查找了。
      • 使用绝对路径指明第三方模块存放的位置,可以减少搜索的步骤
            function resolve(dir) {  // 返回根路径
                return path.join(__dirname, '../', dir)
            }
            module.exports = {
                resolve: {
                    modules: [resolve('src'), resolve('node_modules')]
                }
            }
        
      • 作为优化的方向,其实并不推荐,可能会出现问题,例如你的依赖中还存在node_modules目录,那么就会出现,对应的文件明明在,但是却提示找不到。
    • alias
      resolve.alias通过别名把原导入路径映射成一个新的导入路径。
      • 使用完整的文件路径,可以减少耗时的递归查找;
        resolve: {
             alias: {
                 'react': resolve('./node_modules/react/dist/react.min.js'),
                 'assets': resolve('./public/assets')
             }
        }
        
      • 对应的导入就可以使用别名
        import 'react'
        import 'assets/index.css'
        
    • extensions
      在导入语句时,如果没带后缀名,webpack会自动带上不同的后缀去尝试查找;
      resolve.extensions用于配置查找文件的范围和顺序,默认是['.js', '.json'],例如希望先找.web.js,如果没有,再找.js
      resolve: {
          extensions: ['.web.js', '.js'] // 当然,还可以配置 .json, .css
      }
      
      使用 import dialog from '../dialog'; 时,因为没有带明确的后缀,webpack会自动带上extensions中配置的后缀,首先查找../dialog.web.js,如果不存在,再查找../dialog.js
      这在适配多端的代码中非常有用,否则就需要根据不同的平台引入不同的文件,牺牲了速度。
      同时,应该将高频的后缀放在前面,并且数组不要太长,减少尝试访问的次数。
    • enforceExtension
      配置resolve.enforceExtension 为 true,那么导入语句不能缺省文件后缀。
    • mainFields
      有一些第三方模块会提供多份代码,如bootstrap,查看其package.json
      {
          "style": "dist/css/bootstrap.css",
          "sass": "scss/bootstrap.scss",
          "main": "dist/js/bootstrap",
      }
      
      resolve.mainFields默认配置为['browser', 'main'],即首先查找对应依赖的package.json中的 browser 字段,如果没有,则查找 main 字段。
      import 'bootstrap'默认找的是main: "dist/js/bootstrap",如果希望默认去找css文件的话,则配置resolve.mainFields
      resolve: {
          mainFields: ['style', 'main'] 
      }
      

    相关文章

      网友评论

          本文标题:webpack从配置到跑路v2

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