webpack配置

119 阅读7分钟

问题:

1.Vue-cli3的webpack和vue-cli2webpack配置的差异吗

2.前端代码为何要进行构建和打包?

3.module chunk bundle分别是什么意思,有何区别?

4.loader和plugin的区别?

5.webpack如何实现懒加载?

6.webpack常见的性能优化

7.babel-runtime和babel-polyfill的区别

关于webpack5

webpack5主要是内部效率的优化

对比webpack4,没有太多使用上的改动

可以直接使用webpack5

升级 webpack5 以及周边插件后,代码需要做出的调整:
- package.json 的 dev-server 命令改了 `"dev": "webpack serve --config build/webpack.dev.js"`
- 升级新版本 `const { merge } = require('webpack-merge')`
- `module.rules``loader: {'xxx-loader'}` 换成 `use: {'xxx-loader'}`
- `filename: 'bundle.[contenthash:8].js'` 其中 `h` 小写,不能大写

基本配置(基本配置只能做demo,不能做线上项目)

安全配置

  拆分配置

webpack.dev.js 开发环境配置

webpack.prod.js 生产环境配置

webpack.common.js 公共配置

// webpack.common.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')

module.exports = {
    entry: path.join(srcPath, 'index'),
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: ['babel-loader'],
                include: srcPath,
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                loader: ['style-loader', 'css-loader', 'postcss-loader']
            },
            {
                test: /\.less$/,
                loader: ['style-loader', 'css-laoder', 'less-loader']
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html'
        })
    ]
}

  merge

    安装webpack-merge工具  webpack-merge是一个用于合并webpack配置的工具,在使用webpack进行项目开发时,通常会有多个webpack配置文件,例如开发环境配置、生产环境配置等。webpack-merge提供了一个简单的方法来合并这些配置文件,以便在构建过程中根据需要使用不同的配置。通过使用webpack-merge,你可以将多个配置文件合并为一个配置对象,然后将其传递给webpack进行构建。这样可以避免在不同的配置文件中重复定义相同的配置项,提高配置的可维护性和复用性。

// cnpm install --save-dev webpack-merge

// webpack.dev.js
const webpackCommConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')

module.exports = smart(webpackCommConf, {
    mode: "development",
    module: {
        rules: [
            {
                test: /\.(png|jpg|jpeg|gif)$/,
                use: 'file-loader'            }
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            ENV: JSON.stringify('development')
        })
    ]
})

dev-server 启动本地服务

// 安装webpack-dev-server
cnpm install webpack-dev-server ---save-dev



// webpack.dev.js
devServer: {
    port: 8080,
    progress: true, // 显示打包的进度条
    contentBase: distPath, // 根目录
    open: true, // 自动打开浏览器
    compress: true, // 启动gzip压缩
}

// package.json
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"",
    "devBuild": "webpack --config build-base-conf/webpack.dev.js",
    "dev":"webpack-dev-server --config build-base-conf/webpack.dev.js" ,
    "build": "webpack --config build-base-conf/webpack.prod.js",

    // 设置代理
    "proxy": {
        // 将本地代理 /api/xxx 代理到 localhost:3000/api/xxx
        '/api': 'http://localhost: 3000',
        // 将本地 /api2/xxx 代理到 localhost:3000/xxx
        '/api2': {
            target: 'http://localhost: 3000',
            pathRewrite: {
                '/api2': ''
            }
        }
    }
}

解析ES6

一般在webpack.common.js里用babel-loader处理,用到babel-loader需要配置一个.babelrc的文件

// .babelrc
{
    "presets": ["@babel/preset-env"],
    "plugins": []
}

// webpack.common.js
rules: [
    {
        test: /\.js$/,
        loader: ['babel-loader'],
        include: srcPath,
        exclude: /node_modules/
    }
]

"@babel/preset-env" 是一个特殊的预设,它根据目标环境的配置自动确定要应用的转换规则。它可以根据目标浏览器的版本或其他环境配置,自动选择并应用必要的转换,以确保代码在目标环境中能够运行。 通过将 "presets": ["@babel/preset-env"] 添加到babel配置文件中,你告诉Babel使用 "@babel/preset-env" 这个预设来进行代码转换。这样,Babel将根据目标环境的配置自动确定要应用的转换规则,以确保你的代码在不同环境中具有良好的兼容性。

解析样式

// loader的执行顺序是从后往前的
{
    test: /\.css$/,
    loader: ['style-loader', 'css-loader', 'postcss-loader']
}
// 加postcss-loader需要配置一个文件 和src文件夹同级 postcss.config.js


// postcss.config.js 
module.exports = {
    plugins: [require('autoprefixer')] Calculating...
}

// 主要是给css样式加一些前缀的东西 
// 比如设置transform: rotate(-45deg)到Google浏览器解析的时会加上一行 
// -webkit-transform: rotate(-45deg)

autoprefixer需要安装一下

{
    test: /\.less$/,
    // 增加 'less-loader'(安装一下), 注意顺序 从后往前解析 先解析less语法产出css语法到css文件到style 
    loader: ['style-loader', 'css-loader', 'less-loader']
}

解析 图片文件

// webpack.prod.js
module: {
    rules: [
        {
            test: /\.(png|jpg|jpeg|gif)$/,
            use: {
                loader: 'url-loader',
                options: {
                    // 小于5kb 的图片用那个base64格式产出 
                    // 否则依然沿用 file-loader的形式,产出 url格式
                    limit: 5*1024
                    // 打包到 img 目录下(打包完到img目录下)
                    outputPath: '/img/',
                    // 设置图片的 cdn 地址(也可以统一在外面的 output 中
                    // publicPath: 'http://cdn.abc.com'
                }
            }
        }
    ]
}

// webpack.dev.js
modules: {
    rules: [
        {
            test: /\.(png|jpg|jpeg|gif)$/,
            use: 'file-loader'
        }
    ]
}

file-loader:用于将文件发送到输出目录,并返回文件的公共URL。将图像作为单独的文件保存在输出目录中时,它对于处理图像非常有用。它为每个导入的图像生成一个新文件,并提供访问该文件的URL。

url-loader: 与file-loader类似,但有一个额外的功能。它可以将小图像转换为baseURL,并将它们直接嵌入到捆绑包中,而不是创建单独的文件。这可能有利于提高性能,因为它减少了加载页面所需的HTTP请求数量。但是,较大的图像仍然作为单独的文件发射。

hash的作用

// webpack.prod.js
output: {
    // 打包时加上hash值,如果hash值没变加载的时候走缓存,提高加载速度,8代表hash值有8位
    filename: 'bundle.[contentHash:8].js',
    path: distPath,
}

高级配置

多入口

在webpack.common.js里建入口需要建两个

// webpack.common.js
module.exports = {
    entry: {
        index: path.join(srcPath, 'index.js'),
        pther: path.join(srcPAth, 'other.js')
    },
    plugins: [
        // 多入口 - 生成 index.html
        new HtmlWebpackPlugin({
            template: path.join(srcPath, 'index.html'),
            filename: 'index.html',
            // chunks 表示该页面要引用哪些chunk(即上面的index和other对应的文件)
            chunks: ['index'] // 只引用index.js
        }),
        //  多入口 --- 生成other.html
         new HtmlWebpackPlugin({
            template: path.join(srcPath, 'other.html'),
            filename: 'other.html',
            chunks: ['other'] // 只引用index.js        })
    ]
}

// webpack.dev.js/webpack.prod.js
module.eports = smart(webpackCommonConf, {
    mode: 'production',
    output: {
        filename: '[name].[cntentHash:8].js', // name 即多入口时 entry对应的变量名
        path: distPath
    }
})

// webpack.prod.js
plugin: [
    new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹 
    new webpack.DefinePlugin({
        ENV: JSON.stringly('production')
    })
]

多入口访问就是 localhost:8080/index.html / localhost:8080/other.html

抽离和压缩css

如初级配置那样,没有单独的把css抽离压缩,所以打包完的css是直接写到js文件里的,这样效率比较低,因为要执行js才能把css解析出来,并不科学。

抽离css 去掉在公共配置webpack.common.js中css的处理,在线上环境中webpack.prod.js配置,dev环境下还是沿用之前webpack.common.js中的配置

// webpack.prod.js
const path = require('path')
const webpack = require('webpack')
const { smart } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = reuqire('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack')
const webpackCommonConf = require('./webpack.common.js')
const {srcPath, distPath} = require('./paths')

module.exports = smart(webpackCommonConf, {
    mode: 'production',
    output: {
        filename: '[name].[contentHash:8].js',
        path: distPath,
    },
    module: {
        rules: [
            {
                test: /\.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    ......                
                }
            },
            // 抽离 css
            {
                test: /\.css$/,
                loader: [
                    MiniCssExtractPlugin.loader, // 注意,这里不再用style-loader
                    'css-loader',
                    'postcss-loader'
                ]
            },
            // 抽离 less
            {
                test: /\.less$/,
                loader:[
                    MiniCssExtractPlugin.loader, // 注意,这里不再用style-loader
                    'css-loader',
                    'less-loader',
                    'postcss-loader'
                ]
            }
        ]    },
    plugins: [
        new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹
        new webpack.DefinePlugin({
            ENV: JSON.stringify('production')
        })
        
        // 抽离 css 文件
        new MiniCssExtractPlugin({
            filename: 'css/main.[contentHash:8].css'
        })
    ],
    optimization: {
        // 压缩 css
        minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})]    
    }
})

mini-css-extract-plugin插件应该只在生产环境构建中使用,并且在loader链中不应该有style-loader,特别是我们在开发模式中使用HMR时。

[terser-webpack-plugin](https://www.jianshu.com/p/4ce8e2247033)

抽离公共代码(重要)

抽离公共代码的场景,多入口文件时打包,会把一个公共文件例如sum.js同时都打包进index.min.js 和 other.min.js,所以需要把sum.js单独抽出来 两边相互引用,或者是每个模块都引用了第三方模块,每次打包的时也会把loadash打包进去,基于我们之前配置输出文件hash值的方式,当修改我们自己的业务代码时,无论修改的多不多,hash都会改变,每次由于这样我们改了一点点的业务代码,而导致hash值变,但是我们引入的第三方模块并没有变,这时候也会被打包进去,这个第三模块也会被重新加载会加载很慢,如果单独把第三方模块拎出来,那么再加载的时候,由于第三方模块没有做出修改,那么他命中缓存,就会加载的很快 

// webpack.common.js
plugins: [
    // 多入口 -- 生成index.html
    new HtmlWebpackPlugin({
        template: path.join(srcPath, 'indx.html'),
        filename: 'index.html',
        chunks: ['index', 'vendor', 'common'] // 考虑代码分割
    }),
    // 多入口 -- 生成other.html
    new HtmlWebpackPlugin({
        template: path.join(srcPath, 'other.html'),
        filename: 'other.html',
        chunks: ['other', 'vendor', 'common'] // 考虑代码分割
    })
]

webpack.dev.js开发环境不变,在开发环境下没有必要这么做,在开发环境下希望构建速度更快一点,所以把所有文件一起构建就可以了,不用拆分 也不用压缩 合并 分析,在本地主要就是测试功能。所以拆分抽离压缩合并主要针对线上环境的。

// webpack.prod.js
optimization: {
    // 压缩 css
    minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
    // 分割代码块
    splitChunks: {
        chunks: 'all',
        /**
         *initial 入口是chunk, 对于异步导入的文件不处理
          async 异步chunk, 只对异步导入的文件处理
          all 全部chunk
        */
        cacheGroups: {
            // 第三方模块
            vendor: {
                name: 'vendor', // chunk的名称
                priority: 1, // 权限更高,优先抽离,重要!!!
                test: /node_modules/, // 整个模块的路径,引入的第三方在node_modules里是否能命中
                minSize: 0, // 大小限制,一般情况下可以按照需求写
                minChunks: 1 // 最少复用过几次, 第三方模块引用一次就单独作为一个包里处理
            },
            common: {
                name: 'common', // chunk名称
                priority: 0, // 优先级
                minSize: 0, // 公共模块的大小限制
                minChunks: 2 // 公共模块最少复用过2次 
            }
        }
    }  
}

拆分的时候第三方模块和公共模块可能会有一个冲突,第三方模块可能也作为公共模块去引用,所以用了priority权限 做了一个区分。

懒加载(重要)

在webpack中,懒加载(Lazy Loading)是一种代码分割(Code Splitting)的技术,用于延迟加载某些模块或资源,以优化应用程序的性能。 **懒加载可以将应用程序的代码拆分为多个较小的块(chunks)**,**在需要时动态加载这些块**。这样可以减小初始加载的文件大小,提高应用程序的加载速度,并且仅在需要时才加载额外的模块。 在webpack中,可以使用动态导入(Dynamic Import)语法来实现懒加载。通过在需要懒加载的模块上使用 `import()` 函数,webpack会将该模块单独打包为一个独立的chunk,并在需要时进行异步加载。 例如,假设有一个名为 `lazyModule.js` 的模块需要进行懒加载,可以这样使用: 

import('./lazyModule.js')
    .then(module => { 
        // 在模块加载完成后的回调函数中使用模块 
    }) 
    .catch(error => { 
        // 处理模块加载失败的情况 
    }); 

这样, **`lazyModule.js` 将被打包为一个单独的chunk,**并在 `import()` 被调用时进行异步加载。当加载完成后,可以在 `then` 回调函数中使用该模块。 懒加载在大型应用程序中特别有用,可以根据需要按需加载模块,提高初始加载速度和用户体验。

vue react的异步组件也是如此加载的

回顾一下chunk

产出chunk的地方 entry里面定义chunk

plugins里使用chunk

分割代码的时候产出chunk

处理JSX

在[babel官网](https://www.babeljs.cn/docs/babel-preset-react)上可以搜一下react,安装preset-react

npm install --save-dev @babel/preset-react

安装完在.babelrc文件配置一下, 只要做react应用在.babelrc配置上就可以了

{
    "presets": ["@babel/preset-react"]
}

因为整个的js解析都过babel-loader,过babel-loader时会使用preset-react把JSX语法解析了

处理VUE

用vue-loader,安装完成之后呢就可以在webpack.common.js中针对vue的文件配置下

npm i vue-loader

rules: [
    {
        test: /\.vue$/,
        loader: ['vue-loader']
        include: srcPath
    }
]

module chunk bundle 的区别 

module - 各个源码问价,webpack中一切皆模块

一个module是webpack中的基本构建块,它可以是一个JavaScript文件、一个CSS文件、一个图片文件或其他类型的文件。在webpack中,每个文件都被视为一个module。Webpack使用loader来处理不同类型的模块,并将它们转换为可被浏览器理解的代码。(除了index.html每一个都是模块,html是输出的类似于模板的一个东西)

chunk - 多模块合并成的,如entry import() splitChunk

chunk``主要是在内部用于管理捆绑过程。输出是由bundle由chunk组成,其中有几种类型entry child等。通常,chunk直接与bundle对应,但是有些配置不会产生一对一的关系,例如MiniCssExtractPlugin可从chunk中抽离出css文件,单独生成bundle。生成chunk有三种方式,entry、动态加载、splitChunks抽取共有代码

bundle - 最终的输出文件

bundle``由许多不同的模块生成,包含已经经过加载和编译过程的源文件的最终版本。通常情况下,一个应用程序会生成一个Bundle,它包含了所有的JavaScript、CSS、图片等资源,可以被直接加载到浏览器中运行。