webpack从配置到跑路v2

入口

打包默认的入口文件: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',     // 单出口
    filename: '[name].[hash:5].js',  // 多出口
    
  • publicPath:引用资源的发布路径
    publicPath: 'http://www.xxx.com/'
    
    // 编译构建之后 index.html 与 CSS
    
    background-image: url(http://www.xxx.com/assets/bg.png);
    
    

清理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)