Webpack

一、Webpack 基本使用

Webpack 简介

Webpack是一种前端资源构建工具,一个静态模块打包器(module bundler)。在 Webpack 看来,前端的所有资源文件(js / json / css / img / less / ...)都会作为模块处理,它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。

Webpack 五个核心概念

1.Entry

入口(Entry)指示 Webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图

2.Output

输出(Output)指示 Webpack 打包后的资源 bundles 输出到哪里去,以及如何命名

3.Loader

加载器(Loader)让 Webpack 能够去处理那些非 js 文件(Webpack 自身只理解 js)

4.Plugins

插件(Plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化到压缩,一直到重新定义环境中的变量等

5.Mode

模式(Mode)指示 Webpack 使用相应模式的配置

选型 描述 特点
development process.env.NODE_ENV = development。
启用 NamedChunksPlugin 和 NamedModulesPlugin
代码本地调试运行的环境
production process.env.NODE_ENV = production。
启用 FlagDependencyUsagePlugin,FlagIncludeChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin,OccurrenceOrderPlugin,SideEffectsFlagPlugin 和 UglifyJsPlugin
代码优化上线运行的环境

Webpack 安装

1.全局安装

npm i webpack webpack-cli -g

2.开发环境安装

npm i webpack webpack-cli -D

Webpack 打包指令

1.开发环境打包

webpack ./src/main.js -o ./build/built.js --mode=development

2.生产环境打包

webpack ./src/main.js -o ./build/built.js --mode=production

Webpack 配置文件

// resolve用来拼接绝对路径
const {resolve} = require('path');

// webpack配置
module.exports = {
    // 入口文件
    entry: './src/index.js',
    // 输出
    output: {
        // 输出文件名
        filename: 'built.js',
        // 输出路径
        // __dirname是nodejs 的变量,代表当前文件的目录绝对路径
        path: resolve(__dirname, 'build')
    },
    // loader的配置
    module: {
        rules: [
            // 详细loader配置
        ]
    },
    // plugins的配置
    plugins: [
        // 详细plugins配置
    ],
    mode: 'development'
    // mode: 'production'
}

二、Webpack 开发环境配置

打包样式资源

安装 module

npm i css-loader style-loader less less-loader -D

webpack.config.js

module: {
    rules: [
        // 详细loader配置
        {
            // 匹配css文件
            test: /\.css$/,// 注意这里不要带引号
            // 使用loader进行处理
            use: [
                // use数组中loader执行顺序:从右到左,从下到上,依次执行
                // 创建style标签,将js的样式资源插入进去,添加到head中生效
                'style-loader',
                // 将css文件变成commonjs模块加载到js中,里面的内容时样式字符串
                'css-loader'
            ]
        },
        {
            // 匹配less文件
            test: /\.less$/,
            // 使用loader进行处理
            use: [
                'style-loader',
                'css-loader',
                // 将less文件编译成css文件
                'less-loader'
            ]
        }
    ]
}

打包 HTML 资源

安装 module

npm i html-webpack-plugin -D

webpack.config.js

plugins: [
    // 默认会创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)
    new HtmlWebpackPlugin({
        // 复制'./src/index.html'文件,并自动引入打包输出的所有资源(JS/CSS)
        template: './src/index.html'
    })
],

注意不要在 HTML 页面手动引入 JS/CSS 资源,打包后会自动引入

打包图片资源

安装 module

npm i url-loader file-loader html-loader -D

webpack.config.js

module: {
    rules: [
        {
            test: /\.less$/,
            use: ['style-loader', 'css-loader', 'less-loader']
        },
        {
            test:/\.(png|jpg)$/,
            loader: 'url-loader',
            options: {
                // 图片大小如果小于5kb,就会被base64处理
                // base64优点:减少请求数量(减轻服务器压力)
                // base64缺点:图片体积会更大(文件请求速度更慢)
                limit: 5 * 1024,
                // 给图片重命名
                // [hash:10]指取图片的hash值的前10位
                // [ext]指图片原来的扩展名
                name: '[hash:10].[ext]'
            }
        },
        {
            test: /\.html$/,
            // 处理html文件的img图片(负责引入img,从而能被url-loader进行处理)
            loader: 'html-loader'
        }
    ]
},

打包字体图标资源

使用 module:file-loader

webpack.config.js

module: {
    rules: [
        {
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
        },
        {
            // 排除js|css|html|less|png|jpg资源
            exclude: /\.(js|css|html|less|png|jpg)/,
            loader: 'file-loader',
            options: {
                name: '[hash:10].[ext]'
            }
        }
    ]
},

devServer

开发服务器(devServer):用于自动化

  • 自动编译
  • 自动打开浏览器
  • 自动刷新浏览器

特点:只会在内存中编译打包,不会有任何输出

安装 devServer:

npm i webpack-dev-server -D

启动 devServer 指令:(本地安装启动时使用 npx)

npx webpack-dev-server

devServer 配置:

devServer: {
    // 项目构建后的路径
    contentBase: resolve(__dirname, 'build'),
    // 启动gzip压缩
    compress: true,
    // 端口号
    port: 9527,
    // 自动打开浏览器
    open: true
}

开发环境基本配置

const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/js/index.js',
    output: {
        filename: 'js/built.js',// 
        path: resolve(__dirname, 'build')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.less$/,
                use: ['style-loader', 'css-loader', 'less-loader']
            },
            {
                test: /\.(png|jpg)$/,
                loader: 'url-loader',
                options: {
                    limit: 5 * 1024,
                    name: '[hash:10].[ext]',
                    outputPath: 'img'// 指定打包后输出的路径
                }
            },
            {
                test: /\.html$/,
                loader: 'html-loader'
            },
            {
                exclude: /\.(html|css|js|less|png|jpg)/,
                loader: 'file-loader',
                options: {
                    name: '[hash:10].[ext]',
                    outputPath: 'font'
                }
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ],
    mode: 'development',
    devServer: {
        contentBase: resolve(__dirname, 'build'),
        compress: true,
        open: true,
        port: 9527
    }
}

三、Webpack 生产环境配置

CSS 处理

将 CSS 提取成单独文件

安装 module

npm i mini-css-extract-plugin -D

webpack.config.js

const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    entry: './src/js/index.js',
    output: {
        filename: 'built.js',
        path: resolve(__dirname, 'build')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    // 用这个loader取代style-loader,将js中的css提取成单独文件
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),
        new MiniCssExtractPlugin({
            filename: 'css/main.css'
        })
    ],
    mode: 'development'
};

CSS 兼容性处理

安装 module

npm i postcss-loader postcss-preset-env -D

webpack.config.js

const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    entry: './src/js/index.js',
    output: {
        filename: 'built.js',
        path: resolve(__dirname, 'build')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            ident: 'postcss',
                            plugins: () => [
                                // postcss的插件
                                require('postcss-preset-env')
                            ]
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),
        new MiniCssExtractPlugin({
            filename: 'css/style.css'
        })
    ],
    mode: 'development'
};

package.json 配置

"browserslist": {
    "development": [
        "last 1 chrome version", // 兼容 chrome 最新的版本
        "last 1 firefox version",
        "last 1 safari version"
    ],
    "production": [
        ">0.1%",// 兼容99.9%的浏览器
        "not dead",// 除去不再使用的浏览器
        "not op_mini all"// 除去op_mini所有的浏览器
    ]
}

注意 postcss-loader 默认使用生产环境的配置,要使用开发环境的配置,需要在 webpack.config.js 中设置 nodejs 的环境变量

process.env.NODE_ENV = 'development'

压缩 CSS

安装 module

npm i optimize-css-assets-webpack-plugin -D

webpack.config.js 中引入并使用该插件

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');

plugins: [
    new OptimizeCssAssetsWebpackPlugin()
]

JS 处理

JS 语法检查

安装 module

npm i eslint eslint-loader eslint-config-airbnb-base eslint-plugin-import -D

创建测试的 index.js

function add(x,y) {
  return x+y;
}

console.log(add(1,2));

webpack.config.js

module: {
    rules: [
        {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'eslint-loader',
            options: {
                // 自动修复
                fix: true
            }
        }
    ]
},

注意eslint 只需要检查自己写的源代码,不需要检查第三方库,所以要排除第三方库,即 node_modules

设置检查规则,package.json 配置

"eslintConfig": {
    "extends": "airbnb-base"
}

"extends": "airbnb-base" 指使用 airbnb-base 的语法风格

eslint 识别不了 window、document 等全局变量,打包时会报错,可以在 eslintConfig 中添加配置:

"eslintConfig": {
    "extends": "airbnb-base",
    "env": {
        "browser": true // 支持浏览器端全局变量
    }
}

warning Unexpected console statement no-console:指不期望的 console 语句,开发环境中调试可以,生产环境中不建议使用 console 语句,可以设置不检查

// eslint-disable-next-line

指下一行不进行 eslint 检查

JS 兼容性处理

1.JS 基本兼容性处理

安装 module

npm i babel @babel/preset-env @babel/core -D

webpack.config.js

module: {
    rules: [
        {
            test: /\.js$/,
            loader: 'babel-loader',
            options: {
                // 预设:指定 babel 做什么样的兼容性处理
                presets: ['@babel/preset-env']
            }
        }
    ]
}

这种方式只能转换基本的语法,对于像 promise 这样的新特性不能转换

2.JS 全部兼容性处理

安装 module

npm i @babel/polyfill -D

在入口文件中引入

import '@babel/polyfill'

这种方式会将所有的新特性全部转换,造成打包后的 js 文件提供过大

3.JS 按需加载兼容性处理

安装 module

npm i core-js -D

webpack.config.js

module: {
    rules: [
        {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
                // 预设:指定 babel 做什么样的兼容性处理
                presets: [ // 注意这里需要在数组中嵌套数组
                    [
                        '@babel/preset-env',
                        {
                            // 按需加载
                            useBuiltIns: 'usage',
                            // 指定 core-js 版本
                            corejs: {
                                version: 3
                            },
                            // 指定兼容性做到哪个版本的浏览器
                            targets: {
                                chrome: '60',
                                firefox: '60',
                                ie: '9',
                                safari: '10'
                            }
                        }
                    ]
                ]
            }
        }
    ]
}

这种按需加载的方式打包后的 js 文件会比第二种方式打包后的 js 文件体积小很多(core-js🐮🍺)

压缩 JS

压缩 JS 只需要在 webpack.config.js 中的 mode 改为 production 即可,生产环境下自动压缩 JS 代码

mode: 'production'

HTML 处理

压缩 HTML

webpack.config.js

plugins: [
    new HtmlWebpackPlugin({
        template: './src/index.html',
        // 压缩html代码
        minify: {
            // 移除空格
            collapseWhitespace: true,
            // 移除注释
            removeComments: true
        }
    })
]

生产环境基本配置

const {resolve} = require('path');
const HtmlWebpackPlugin  =require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
// 抽取共用的 loader
const commonCssLoader = [
    // 将 CSS 抽取成单独文件
    {
        loader: MiniCssExtractPlugin.loader,
        options: {
            publicPath: '../' // 解决打包后的图片路径问题
        }
    },
    'css-loader',
    // CSS 兼容性处理
    {
        loader: 'postcss-loader',
        options: {
            ident: 'postcss',
            plugins: () => [
                require('postcss-preset-env')
            ]
            // 需要在 package.json 中指定 browserslist:即兼容哪些版本的浏览器
        }
    }
];

process.env.NODE_ENV = 'production';

module.exports = {
    entry: './src/js/index.js',
    output: {
        filename: 'js/built.js',
        path: resolve(__dirname, 'build'),
    },
    module: {
        rules: [
            // 处理 CSS 资源
            {
                test: /\.css$/,
                use: [...commonCssLoader]
            },
            // 处理 less 资源
            {
                test: /\.less$/,
                use: [...commonCssLoader, 'less-loader']
            },
            // JS 语法检查
            {
                test: /\.js$/,
                exclude: /node_modules/,
                enforce: 'pre', // 为了防止冲突,指定 eslint-loader 先执行
                loader: 'eslint-loader',
                // 需要在 package.json 中指定 eslintConfig
                options: {
                    fix: true
                }
            },
            // JS 兼容性处理
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                options: {
                    presets: [
                        [
                            '@babel/preset-env',
                            {
                                useBuiltIns: 'usage',
                                corejs: {
                                    version: 3
                                },
                                targets: {
                                    chrome: '60',
                                    ie: '9'
                                }
                            }
                        ]
                    ]
                }
            },
            // 处理图片资源
            {
                test: /\.(jpg|png)/,
                loader: 'url-loader',
                options: {
                    limit: 5 * 1024,
                    name: '[hash:10].[ext]',
                    outputPath: 'img'
                }
            },
            // 处理html中的img资源
            {
                test: /\.html$/,
                loader: 'html-loader',
            },
            // 处理其他资源
            {
                exclude: /\.(html|js|css|less|jpg|png)/,
                loader: 'file-loader',
                options: {
                    name: '[hash:10].[ext]',
                    outputPath: 'font',
                }
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            minify: {
                collapseWhitespace: true,
                removeComments: true
            }
        }),
        // 抽取 CSS
        new MiniCssExtractPlugin({
           filename: 'css/built.css'
        }),
        // 压缩 CSS
        new OptimizeCssAssetsWebpackPlugin()
    ],
    mode: 'production'
}

四、Webpack 性能优化

开发环境性能优化

  • 优化打包构建速度
    • HMR
  • 优化代码调试
    • source-map

生产环境性能优化

  • 优化打包构建速度
    • oneOf
    • babel 缓存
    • 多进程打包
    • externals
    • dll
  • 优化代码运行性能
    • 文件资源缓存
    • tree-shaking
    • code split
    • 懒加载和预加载
    • PWA

HMR

HMR:hot modules replacement,热模块更新/模块热更新,用来当某个文件更新以后,打包时只打包那一个文件,而不用打包所有文件,极大提升打包速度

HMR 只能在开发环境中使用,devServer 内部支持 HMR,只需要开启即可

devServer: {
    // 开启 HMR
    hot: true
}

开启后,更新样式文件

image-20200716153242444

可以看到打包时只打包了样式文件,页面也没有刷新,这是因为 style-loader 内部实现了 HMR

更新 JS 文件,页面刷新了,因为 JS 默认不支持 HMR 功能,要想实现 HMR,可以为需要的 JS 文件添加监听事件

if(module.hot){
    module.hot.accept('./print.js', function(){
        print();
    })
}

如果 module.hot 为 true,说明开启了 HMR 功能,此时添加监听事件监听 print.js 文件的变化,如果发生变化,其他的文件不会重新打包构建,然后会执行后面的回调函数,将需要更新的代码放入其中,注意只能监听非入口 JS 文件,入口文件更新后,会重新打包构建

开启 HMR 后,修改 HTML 文件后页面不生效,可以修改 entry 入口,将 HTML 文件引入

entry: ['./src/js/index.js', './src/index.html']

因为项目一般只有一个HTML 文件,当 HTML 文件发生变化时,所有的文件都会发生变化,所以 HTMl 文件不用实现 HMR

source-map

source-map:一种提供源代码到构建后代码映射的技术,如果构建后代码出错了,可以通过映射追踪到源代码错误

开启 source-map:

webpack.config.js 中配置

devtool: 'source-map'

source-map 分类:

  • 外部

    1. source-map

      提供错误代码准确信息和源代码的错误位置

    2. hidden-source-map

      提供错误代码准确信息,但是没有错误位置,不能追踪源代码错误,只能提示构建后代码的错误位置,只隐藏源代码

    3. nosources-source-map

      提供错误代码准确信息,但是没有任何错误位置,隐藏源代码和构建后代码

    4. cheap-source-map

      提供错误代码准确信息和源代码的错误位置,但是只能精确到行

    5. cheap-module-source-map

      提供错误代码准确信息和源代码的错误位置,并且加入了 loader 的 source-map,也只能精确到行

  • 内联

    1. inline-source-map

      只生成一个内联 source-map,提供错误代码准确信息和源代码的错误位置

    2. eval-source-map

      每一个文件都生成对应的 source-map,提供错误代码准确信息和源代码的错误位置

开发环境使用 source-map:

要求:速度快,调试更友好,使用 eval-source-map / eval-cheap-module-source-map

生产环境使用 source-map:

内联会让代码体积变大,所以在生产环境不用内联

考虑:源代码是否隐藏,调试是否友好,使用 source-map / cheap-module-surce-map

oneOf

  • 使用 oneOf 根据文件类型加载对应的 loader,只要匹配一个即可退出
  • 对于同一类型文件,比如处理 js,需要多个 loader,可以抽离出其中一个,确保 oneOf 里面一种文件类型只对应一个 loader

webpack.config.js

module: {
    rules: [
        // 将 eslint-loader 放在oneOf外面,指定先执行
        {
            test: /\.js$/,
            exclude: /node_modules/,
            enforce: 'pre', // 为了防止冲突,指定 eslint-loader 先执行
            loader: 'eslint-loader',
            // 需要在 package.json 中指定 eslintConfig
            options: {
                fix: true
            }
        },
        {
            oneOf: [
                {
                    test: /\.css$/,
                    use: [...commonCssLoader]
                },
                {
                    test: /\.less$/,
                    use: [...commonCssLoader, 'less-loader']
                },
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            [
                                '@babel/preset-env',
                                {
                                    useBuiltIns: 'usage',
                                    corejs: {
                                        version: 3
                                    },
                                    targets: {
                                        chrome: '60',
                                        ie: '9'
                                    }
                                }
                            ]
                        ]
                    }
                },
                {
                    test: /\.(jpg|png)/,
                    loader: 'url-loader',
                    options: {
                        limit: 5 * 1024,
                        name: '[hash:10].[ext]',
                        outputPath: 'img'
                    }
                },
                {
                    test: /\.html$/,
                    loader: 'html-loader',
                },
                {
                    exclude: /\.(html|js|css|less|jpg|png)/,
                    loader: 'file-loader',
                    options: {
                        name: '[hash:10].[ext]',
                        outputPath: 'font',
                    }
                }
            ]
        }
    ]
}

缓存

缓存分为 babel 缓存和文件资源缓存

babel 缓存

babel-loader 添加配置

options: {
    cacheDirectory: true
}

文件资源缓存

  1. hash:每次 webpack 构建时会生成唯一的 hash 值,但是 JS 和 CSS 使用同一个 hash 值,如果只改动一个文件然后重新打包,会导致所有缓存失效
  2. chunkhash:根据 chunk 生成的 hash 值,如果打包来源于同一个 chunk,那么 hash 值就一样,因为 CSS 是在 JS 中引入,所以同属于一个 chunk
  3. contenthash:根据文件的内容生成的 hash 值,不同文件的 hash 值肯定不一样

tree shaking

tree-shaking 用于移除 JS 上下文中的未引用代码,它依赖于 ES6 模块化,减少代码体积

使用 tree-shaking:

  1. 必须使用 ES6 模块化
  2. 开启 production 环境

为了避免造成误杀,可以在 package.json 中配置 sideEffects

"sideEffects": ["*.CSS", "*.less"]

code split

code split 用于把代码分离到不同的 bundle 中,可以按需加载或并行加载这些文件

通过多入口拆分

对象形式多入口,有一个入口,最终输出就有一个 bundle

entry: {
    index: './src/js/index.js',
    test: './src/js/test.js'
},
output: {
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build'),
}

单入口配置 splitChunks

可以将 node_modules 中的代码单独打包成一个 chunk 输出

optimization: {
    splitChunks: {
        chunks: 'all'
    }
}

单入口打包时会将入口文件与在入口文件中引入的其他 js 文件打包为一个 chunk,可以使用 import 动态导入语法,将引入的文件单独打包为一个 chunk

index.js

import('./test').then(({reduce}) => {
  console.log(reduce(1,2,3,4,5));
}).catch(() => {
  console.log('文件加载失败');
});

打包后生成的 JS 文件是通过 id 来命名的,可以通过 webpack 的魔法注释来指定名称

import(/* webpackChunkName:'test'*/'./test')

多入口配置 splitChunks

自动分析多入口 chunk 中,有没有公共的文件(至少大于30kb),如果有会单独打包成一个 chunk

懒加载和预加载

懒加载和预加载基于代码分割

正常加载:可以认为是并行加载(同一时间加载很多文件),没有先后顺序

懒加载:当文件需要使用时才加载

document.getElementById('btn').onclick = function () {
  import(/* webpackChunkName:'test'*/'./test').then(({reduce}) => {
    console.log(reduce(1,2,3,4,5));
  }).catch(() => {
    console.log('文件加载失败');
  });
};

预加载:等其他资源加载完毕,浏览器空闲时,再进行加载(兼容性较差)

import(/* webpackChunkName:'test', webpackPrefetch: true*/'./test')

配置 webpackPrefetch 为 true 开启预加载

PWA

PWA:渐进式 web 应用,离线可访问技术

安装 module

npm i workbox-webpack-plugin -D

webpack.config.js

const WorkboxWebpackPlugin = require('workbox-webpack-plugin');

plugins: [
    new WorkboxWebpackPlugin.GenerateSW({
        // 删除旧的 serviceWorker
        clientsClaim: true,
        // 使 serviceWorker 快速启动
        skipWaiting: true
    })
]

打包后会生成一个 serviceWorker 配置文件

index.js

// 注册 serviceWorker
// 处理兼容性问题
// navigator 对象包含有关浏览器的信息
if('serviceWorker' in navigator){
  window.navigator.serviceWorker.register('./service-worker.js')
      .then(() => {
        console.log('sw注册成功')
      }).catch(() => {
        console.log('sw注册失败')
      })
}

serviceWorker 代码必须运行在服务器上

可以通过 npm 安装 serve 包,通过 serve 启动

npm i serve -D
serve ./build

或者使用 express

npm i express -D
const express = require('express');
const app  = express();
app.use(express.static('build'));
app.listen(3000);

启动后刷新浏览器即可看到

image-20200717113050353

多进程打包

安装 module

npm i thread-loader -D

一般用于 babel-loader

webpack.config.js

module: {
    rules: [
        {
            test: /\.js$/,
            use: [
                {
                    loader: 'thread-loader',
                    options: {
                        workers: 2 // 进程2个
                    }
                },
                {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            [
                                '@babel/preset-env',
                                {
                                    useBuiltIns: 'usage',
                                    corejs: {
                                        version: 3
                                    },
                                    targets: {
                                        chrome: '60',
                                        ie: '9'
                                    }
                                }
                            ]
                        ]
                    }
                }
            ]
        }
    ]
}

开启多进程打包,进程启动事件大概为600ms,进程通信也有开销,只有工作消耗时间比较长时,才需要多进程打包

externals

防止将某些 import 的包打包到 bundle 中,而是在运行时再去外部获取这些扩展依赖(external dependencies)

例如,从 CDN 引入 JQuery,而不把它打包

index.html

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

webpack.config.js

externals: {
    // 库名: npm 包名
    jquery: 'jQuery'
}

这样仍然可以使用 jQuery

import $ from 'jquery'

dll

在通常的打包过程中,会将第三方库也打包进 bundle 中,由于这些库的内容基本不会改变,每次都打包它们会造成性能的浪费。dll 可以在第一次打包时将所有的第三方库单独打包,而之后的打包则只打包自己编写的内容,可以和 code split 配合拆分文件

webpack.dll.js

const {resolve} = require('path');
const webpack = require('webpack');

module.exports = {
    entry: {
        jquery: ['jquery']
    },
    output: {
        filename: '[name].js',
        path: resolve(__dirname, 'dll'),
        library: '[name]_[hash]'// 打包的库里面向外暴露的变量的名称
    },
    plugins: [
        new webpack.DllPlugin({
            name: '[name]_[hash]',// 映射库的暴露的内容名称
            path: resolve(__dirname, 'dll/manifest.json')// 提供打包的库的映射
        })
    ],
    mode: 'production'
}

webpack 进行打包时,默认运行 webpack.config.js 配置文件,想要运行 webpack.dll.js,需要通过 --config 指定配置文件

webpack --config webpack.dll.js

打包后生成的 dll 文件夹中包含 jquery.js 和 manifest.json

引入打包生成的 jquery.js,需要安装一个 module

npm i add-asset-html-webpack-plugin -D

webpack.config.js

plugins: [
    // 告诉webpack哪些库不参与打包,同时使用时的名称也要变
    new webpack.DllReferencePlugin({
        manifest: resolve(__dirname, 'dll/manifest.json')
    }),
    // 将某个文件打包输出,并在html中自动引入该资源
    new AddAssetHtmlWebpackPlugin({
        filepath: resolve(__dirname, 'dll/jquery.js')
    })
]

五、Webpack 配置详解

entry 详细配置

entry:入口起点,用于指定入口文件,可以配置单入口和多入口,有以下几种类型

1.String 类型

entry: './src/index.js'

单入口,打包形成一个 chunk,输出一个 bundle 文件,此时 chunk 的默认名称是 main

2.array 类型

entry: ['./src/index.js', './src/test.js']

多入口,所有入口文件最终只会形成一个 chunk,输出一个 bundle 文件,只能用于 HMR 功能中使 HTML 热更新生效

entry: ['./src/index.js', './src/index.html']

3.Object 类型

entry: {
    index: './src/index.js',
    test: './src/test.js'    
}

多入口,有几个入口文件就形成几个 chunk,输出几个 bundle 文件,此时 chunk 的名称是对应入口文件的名称

特殊用法:

entry: {
    index: ['./src/index.js', './src/add.js'],
    test: './src/test.js'    
}

output 详细配置

webpack.config.js

output: {
    // 文件名称(指定名称+目录)
    filename: 'js/[name].js',
    // 输出文件目录(所有资源输出的公共目录)
    path: resolve(__dirname, 'dist'),
    // 所有资源引入公共路径的前缀
    publicPath: '/',
    // 非入口chunk的名称
    chunkFilename: 'js/[name]_chunk.js',
    // 整个库向外暴露的变量名    
    library: '[name]',    
    // 变量名添加到window属性上  browser
    libraryTarget: 'window',
    // 变量名添加到global属性上  node
    libraryTarget: 'global',
    // 变量通过commonjs的方式向外暴露
    libraryTarget: 'commonjs',
}

module 详细配置

webpack.config.js

module: {
    rules: [
        {
            test: /\.js$/,
            // 单个loader用loader
            loader: 'eslint-loader',
            // 排除 node_modules 下的js文件
            exclude: /node_moduules/,
            // 只检查 src 下的js文件
            include: resolve(__dirname, src),
            // 优先执行
            enforce: 'pre',
            // 延后执行
            enforce: 'post',
            // 不写则是中间执行
            // loader 的配置
            options: {

            }    
        },
        {
            test: /\.css$/,
            // 多个loader用use
            use: ['style-loader', 'css-loader']
        },
        {
            // 以下配置只会生效一个
            oneOf: []
        }
    ]
}

resolve 详细配置

resolve 指解析模块的规则

webpack.config.js

resolve: {
    //  配置解析模块的路径别名
    alias: {
        // 配置绝对路径,用@代替src
        @: resolve(__dirname, 'src')
    },
    // 配置省略文件路径的后缀名    
    extensions: ['js', 'json', 'jsx', 'css']
    // 告诉 webpack 解析模块的目录路径
    modules: [resolve(__dirname), '../../node_modules'), 'node_modules']
}

devServer 详细配置

devServer 用于开发环境

webpack.config.js

devServer: {
    // 运行代码的目录
    contentBase: resolve(__dirname, 'build'),
    // 监视 contentBase 下的目录,一旦发生变化就会 reload
    watchContentBase: true,
    watchOptions: {
        // 忽略 node_modules 下的文件
        ignored: 'node_modules'
    },
    // 启动 gzip 压缩
    compress: true,
    // 端口号
    port: 9527,
    // 域名
    host: 'localhost',
    // 自动打开浏览器
    open: true,
    // 开启 HMR 功能
    hot: true,
    // 不显示启动服务器日志信息
    clientLogLevel: 'none',
    // 除了基本启动信息外,其他内容都不显示
    quiet: true,
    // 如果出错了,不全屏提示
    overLay: false,
    // 服务器代理,解决开发环境跨域问题
    proxy: {
        // 如果devServer(9527)服务器接收到 /api/xxx 的请求,就会把请求转发到另一个服务器(8080)
        '/api': {
            target: 'http://localhost:8080',
            // 发送请求时,请求路径重写:将 /api/xxx 重写为 /xxx (去掉/api)
            pathRewrite: {
                '^/api': ''
            }
        }
    }
}

optimization 详细配置

optimization 用于生产环境

contenthash 存在的问题:修改 a 文件导致 b 文件的 contenthash 改变。在入口文件 index.js 中引入 a.js,打包后 index.js 中记录的 a.js 的 hash 值,修改 a.js 后重新打包,因为 a.js 的 hash 值发生变化,所以 index.js 中记录的 a.js 的 hash 值也会发生变化,导致 index.js 也会重新打包,造成缓存失效

解决方法:runtimeChunk,将当前模块记录的其他模块的 hash 值单独打包为一个 runtime.js 文件,修改 a.js 后也只会影响 runtime.js,不会影响到 index.js

webpack.config.js

output: {
  filename: 'js/[name].[contenthash:10].js',
  path: resolve(__dirname, 'build'),
  chunkFilename: 'js/[name].[contenthash:10]_chunk.js' // 指定非入口文件的名称
},
optimization: {
  splitChunks: {
    chunks: 'all',
    /* 以下都是splitChunks默认配置,可以不写
    miniSize: 30 * 1024, // 分割的chunk最小为30kb(大于30kb的才分割)
    maxSize: 0, // 最大没有限制
    minChunks: 1, // 要提取的chunk最少被引用1次
    maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量为5
    maxInitialRequests: 3, // 入口js文件最大并行请求数量
    automaticNameDelimiter: '~', // 名称连接符
    name: true, // 可以使用命名规则
    cacheGroups: { // 分割chunk的组
      vendors: {
        // node_modules中的文件会被打包到vendors组的chunk中,--> vendors~xxx.js
        // 满足上面的公共规则,大小超过30kb、至少被引用一次
        test: /[\\/]node_modules[\\/]/,
        // 优先级
        priority: -10
      },
      default: {
        // 要提取的chunk最少被引用2次
        minChunks: 2,
        prority: -20,
        // 如果当前要打包的模块和之前已经被提取的模块是同一个,就会复用,而不是重新打包
        reuseExistingChunk: true
      }
    } */
  },
  // 将index.js记录的a.js的hash值单独打包到runtime文件中
  runtimeChunk: {
    name: entrypoint => `runtime-${entrypoint.name}`
  },
  minimizer: [
    // 配置生产环境的压缩方案:js/css
    new TerserWebpackPlugin({
      // 开启缓存
      cache: true,
      // 开启多进程打包
      parallel: true,
      // 启用sourceMap(否则会被压缩掉)
      sourceMap: true
    })
  ]
}

六、Webpack5 介绍和使用

此版本重点关注以下内容:

  • 通过持久缓存提高构建性能
  • 使用更好的算法和默认值来改善长期缓存
  • 通过更好的树摇和代码生成来改善捆绑包大小
  • 清除处于怪异状态的内部结构,同时在 v4 中实现功能而不引入任何重大更改
  • 通过引入重大更改来为将来的功能做准备,以使我们能够尽可能长时间地使用 v5

下载

npm i webpack@next webpack-cli -D

自动删除 Node.js Polyfills

早期,webpack 的目标是允许在浏览器中运行大多数 node.js 模块,但是模块格局发生了变化,许多模块用途现在主要是为前端目的而编写的。webpack <= 4 附带了许多 node.js 核心模块的 polyfill,一旦模块使用任何核心模块(即 crypto 模块),这些模块就会自动应用。

尽管这使使用为 node.js 编写的模块变得容易,但它会将这些巨大的 polyfill 添加到包中。在许多情况下,这些 polyfill 是不必要的。

webpack 5 会自动停止填充这些核心模块,并专注于与前端兼容的模块。

迁移:

  • 尽可能尝试使用与前端兼容的模块。
  • 可以为 node.js 核心模块手动添加一个 polyfill。错误消息将提示如何实现该目标。

chunk 和模块 ID

添加了用于长期缓存的新算法。在生产模式下默认情况下启用这些功能。

chunkIds: "deterministic", moduleIds: "deterministic"

chunk ID

你可以不用使用 import(/* webpackChunkName: "name" */ "module") 在开发环境来为 chunk 命名,生产环境还是有必要的

webpack 内部有 chunk 命名规则,不再是以 id(0, 1, 2)命名了

tree shaking

1.webpack5 能处理嵌套模块的 tree shaking

// inner.js
export const a = 1;
export const b = 2;

// module.js
import * as inner from './inner';
export { inner };

// user.js
import * as module from './module';
console.log(module.inner.a);

在生产环境中, inner 模块暴露的 b 会被删除

2.webpack5 能处理多个模块之间的关系

import { something } from './something';

function usingSomething() {
  return something;
}

export function test() {
  return usingSomething();
}

当设置了"sideEffects": false时,一旦发现test方法没有使用,不但删除test,还会删除"./something"

3.webpack5 能处理对 commonjs 的 tree shaking

output

webpack4 默认只能输出 ES5 代码

webpack5 开始新增一个属性 output.ecmaVersion, 可以生成 ES5 和 ES6 / ES2015 代码

如:output.ecmaVersion: 2015

SplitChunk

webpack5 代码分割可以指定不同文件最小体积

// webpack4
minSize: 30000;
// webpack5
minSize: {
  javascript: 30000,
  style: 50000,
}

cache

// 配置缓存
cache: {
  // 磁盘存储
  type: "filesystem",
  buildDependencies: {
    // 当配置修改时,缓存失效
    config: [__filename]
  }
}

缓存将存储到 node_modules/.cache/webpack

监视输出文件

webpack4 总是在第一次构建时输出全部文件,但是监视重新构建时会只更新修改的文件。

webpack5 在第一次构建时会找到输出文件看是否有变化,从而决定要不要输出全部文件。

webpack.config.js 配置文件默认值

entry: './src/index.js',
output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
}

更多内容

webpack5

posted @ 2020-07-21 08:31  codeDD  阅读(134)  评论(0编辑  收藏  举报