在webpack等打包工具出世之前,我们普通的H5项目是怎么处理错综复杂的脚本呢? 第一种方式:引用不同的脚本去使用不同的功能,但脚本太多的时候会导致网络瓶颈 第二种方式:使用一个大型js文件去引入所有代码,但这样会严重影响可读性,可维护性,作用域。
举个栗子: 由于浏览器不能直接解析less文件,我们可通过引入转换的插件(file watcher)把less实时转换为css并引入,但项目里面会多出一个map跟css文件,造成项目文件的臃肿。
用官方文档的说法:
node.js诞生可以让Javasrcipt在浏览器环境之外使用,而webpack运行在node.js中。CommonJS的require机制允许在文件中引用某个模块,如此一来就可以解决作用域的问题。
const HtmlWebpackPlugin = require('html-webpack-plugin')
webpack 关心性能和加载时间;它始终在改进或添加新功能,例如:异步地加载 chunk 和预取,以便为你的项目和用户提供最佳体验
webpack有7个核心概念:
新建一个build文件夹,里面新建一个webpack.config.js
这是打包的入口文件,所有的脚本将从这个入口文件开始
const path = require('path')
module.exports = {
entry: path.resolve(__dirname, '../src/main.js')
}
使用对象语法配置,更好扩展,例如一个项目有前台网页跟后台管理两个项目可用多入口管理。
entry: {
app: path.resolve(__dirname, '../src/main.js'),
admin: path.resolve(__dirname, '../src/admin.js'),
},
打包后输出的文件,[name]跟[hash:8]表示文件名跟入口的保持一致但后面加上了hash的后缀让每次生成的文件名是唯一的。
module.exports = {
output: {
filename: '[name].[hash:8].js',
path: path.resolve(__dirname, '../dist'),
},
}
module.exports = {
entry: {
app: './src/app.js',
admin: './src/admin.js',
},
output: {
filename: '\[name].js',
path: \_\_dirname + '/dist',
},
};
// 写入到硬盘:./dist/app.js, ./dist/admin.js
用于模块的源码的转换,也是同学们在配置webpack的时候频繁接触的一个配置项。
举个例子,加载typescript跟css文件需要用到ts-loader跟css-loader、style-loader, 如果没有对应的loader,打包会直接error掉。
我们可以这么配置:先 npm i css-loader style-loader
module: {
rules: [
{
test: /\.css\$/,
use: ['style-loader','css-loader']
},
{
test: /\.ts\$/,
use: 'ts-loader'
}
必须留意的是,loader的执行是从右到左,就是css-loader执行完,再交给style-loader执行,
这是webpack配置的核心,有一些loader无法实现的功能,就通过plugin去扩展,建立一个规范的插件系统,能让你每次搭建项目的时候省去很多成本。
举个例子,我们会使用HtmlWebpackPlugin这个插件去生成一个html,其中会引入入口文件main.js。 假设不用这个插件,会发生什么?
当然是不会生成这个html,因此HtmlWebpackPlugin插件也是webpack的必备配置之一
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html') // 同时引入main.js的hash名字
}),
],
mode一共有production,development,node三种,如果没有设置,会默认为production
不同的mode对于默认优化的选项有所不同,环境变量也不同,具体需要了解每个插件的具体使用
选项 |
描述 |
development |
会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development |
production |
会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin |
none |
没优化选项 |
module.exports = {
mode: 'production'
}
Sourcemap 本质上是一个信息文件,里面储存着代码转换前后的对应位置信息。它记录了转换压缩后的代码所对应的转换前的源代码位置,是源代码和生产代码的映射。 Sourcemap 解决了在打包过程中,代码经过压缩,去空格以及 babel 编译转化后,由于代码之间差异性过大,造成无法debug的问题
当mode为development时,devtool默认为‘eval’,当mode为production时,devtool默认为false。
内联和外部的区别: 外部生成了文件(.map),内联没有。内联构建速度更快。
笔者用的两种配置分为是
// webpack.dev.js
devtool: 'eval-cheap-module-source-map',
// webpack.prod.js
devtool: 'cheap-module-source-map'
Webpack 支持所有符合 ES5 标准 的浏览器(不支持 IE8 及以下版本)。webpack 的 import() 和 require.ensure() 需要 Promise。如果你想要支持旧版本浏览器,在使用这些表达式之前,还需要提前加载 polyfill
本文使用的是webpack5 ,要求Node.js V在10.13.0+
笔者汇总了一部分常用的Loader以及其配置事项
在配置loader前,先了解一下基本的配置
譬如把const转为浏览器认识的var,虽然现在大部分主流浏览器都认识ES5之后的语法。
npm i babel-loader @babel/preset-env @babel/core
在rules配置:
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
},
exclude: /node_modules/
}
如果只有babel-loader,浏览器并不能识别出新的API(promise,proxy,includes),如图:
因此还需要配置一个babel/polyfill,在入口里面:
// npm i @babel/polyfill
entry: ["@babel/polyfill",path.resolve(__dirname, '../src/main.js')],
vue-loader: 解析vue vue-template-compiler: 编译vue模板
npm i vue-loader vue-template-compiler vue-style-loader
npm i vue
在rules跟plugins配置:
const { VueLoaderPlugin } = require('vue-loader') // vue3的引入跟vue2路径不同
rules:{
{
test: /\.vue$/,
use: ['vue-loader']
}
},
plugins:[
...
new VueLoaderPlugin()
...
]
配置完成后,vue文件就可以正常解析了
需要引入的Loader不止一个
modules.exports = {
modules: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
] // 解析css文件必须的style-loader,css-loader
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
'postcss-preset-env' // 解决css不同浏览器兼容性
],
],
},
}
}, 'less-loader'
]
},
}
}
mini-css-extract-plugin: 把css拆分出来用外链的形式引入css文件,然后会在dist生成css文件,为每一个包含了 CSS 的 JS 文件创建一个 CSS 文件, ps: 使用该插件不能重复使用style-loader
···
plugins: [
...
new MiniCssExtractPlugin({
filename: devMode ? "[name].css" : "[name].[hash].css",
chunkFilename: devMode ? "[id].css" : "[id].[hash].css",
}),
...
],
module:{
rules: [{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
// 'style-loader',
'css-loader',
'postcss-loader'
] // 解析css文件必须的style-loader,css-loader
}]
}
file-loader: 就是将文件在进行一些处理后(主要是处理文件名和路径、解析文件url),并将文件移动到输出的目录中
url-loader 一般与file-loader搭配使用,功能与 file-loader 类似,如果文件小于限制的大小。则会返回 base64 编码,否则使用 file-loader 将文件移动到输出的目录中
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
use: [{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}],
include: [resolve('src/assets/image')],
exclude: /node_modules/
},
{
test: /\.(jpg|png|gif)$/i,
use: [{
loader: 'url-loader',
options: {
limit: 10240, // KB
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}],
include: [resolve('src/assets/image')],
exclude: /node_modules/
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
use: [{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
}
}],
include: [resolve('src/assets/icon')],
exclude: /node_modules/
},
js是单线程的工程,在构建工程的过程中,要消耗大量的时间在Loader的转换过程中,为了提升构建速度,这里使用了thread-loader将任务拆分为多个线程去处理。 其原理是把任务分配到各个worker线程中,之前多数人会使用happyPack,但webpack官方使用了thread-loader取代happypack。
...
{
test: /\.js$/,
use: [{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', ],
cacheDirectory: true,
}
},
{
loader: 'thread-loader', // 多线程解析模块
options: {
workers: 3, // 开启几个 worker 进程来处理打包,默认是 os.cpus().length - 1
}
}
],
exclude: /node_modules/
}
...
配置plugins必须注意的是,由于我们的模式(mode)区分为development跟production,因此plugins也需要按照实际需要,在config(公用),dev,prod三个配置文件分开加入。
首先先配置公用部分的plugins
每次执行npm run build 会发现dist文件夹里会残留上次打包的文件,在打包输出前清空文件夹
const {
CleanWebpackPlugin
} = require('clean-webpack-plugin')
当一个html文件里面的css太多,全部把css添加到html中会显得很臃肿,那我们可以用mini-css-extract-plugin 把css拆分成外链引入,为每一个包含了 CSS 的 JS 文件创建一个 CSS 文件。
需要留意的是不能跟style-loader同时使用,下面用了hash
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
....
plugins: [
new MiniCssExtractPlugin({
filename: devMode ? "[name].css" : "[name].[hash].css",
chunkFilename: devMode ? "[id].css" : "[id].[hash].css",
}, {
filename: devMode ? "[name].css" : "[name].[hash].less",
chunkFilename: devMode ? "[id].css" : "[id].[hash].less",
}),
]
....
module:{
rules:[{
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
// 'style-loader',
'css-loader',
'postcss-loader'
]
},
}]
}
....
wepback必备的插件之一,上述举例也有提到。 主要是生产打包后的html, 同时由于main.js文件会随机生成新的hash名字,html在引入main.js文件时频繁改名字会很浪费时间,此插件会自动同步改文件名
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html') // 同时引入main.js的hash名字
}),
],
当我们修改文件的内容时,要重新build一次才能看到变化,这样对开发的效率不友好。
需要留意的是,webpack-dev-server只是在开发环境搭建一个服务帮助开发人员提高开发效率,实现了实时更新的功能,在生产环境并不会用到这一个插件.
同时注意在plugins中加入webpack自带的
HotModuleReplacementPlugin。
webpack-dev-server这个插件功能十分强大,官方文档有详细的记录 (
webpack.docschina.org/configurati…)
npm i webpack-dev-server --save-dev
module.exports = {
devServer: {
// 基本目录
static: {
directory: path.join(__dirname, 'dist'),
},
// 自动压缩代码
compress: true,
port: 9000,
// 自动打开浏览器
open: true,
// 热加载,默认是true
hot: true,
},
plugins: [
new Webpack.HotModuleReplacementPlugin()
]
}
由于生产环境对性能的要求跟开发不同,需要引入的插件比较丰富,也更需要对项目构建有更高的熟悉程度
webpack mode设置production的时候会自动压缩js代码。原则上不需要引入terser-webpack-plugin进行重复工作。但是
optimize-css-assets-webpack-plugin压缩css的同时会破坏原有的js压缩,所以这里我们引入terser-webpack-plugin进行压缩
option很多,使用了dropconsole去除打印的内容
const TerserPlugin = require("terser-webpack-plugin");
...
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress:{
drop_console: true
}
}
}),
],
},
...
前面有使用mini-css-extract-plugin的插件去拆分css,但这个插件并不能压缩CSS体积, 使用
css-minimizer-webpack-plugin 可以压缩css的体积,但不同的是它是被加入到optimization的minimizer中,跟上述的js压缩插件共同作用
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
...
optimization: {
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin({
terserOptions: {
compress:{
drop_console: true
}
}
}),
],
},
...
使用DllReferencePlugin把不需要经常变更的静态文件抽离出来,譬如element-ui组件,这样每次打包的时候就不会再去重新打包选中的静态文件了。
如此一来,当我们修改代码后,webpack只需要打包项目的代码而不需要重复去编译没有发生改变的第三方库。这样当我们没有升级第三方库时,webpack就不会再对这些库进行打包,从而提升项目构建的速度。
首先我们在同级目录下新建文件webpack.dll.config.js,在entry的vendor里面配置了vue跟element-ui。
// webpack.dll.config.js
const path = require("path");
const webpack = require("webpack");
// 每次执行npm run build 会发现dist文件夹里会残留上次打包的文件,在打包输出前清空文件夹
const {
CleanWebpackPlugin
} = require('clean-webpack-plugin')
module.exports = {
mode: 'production',
// 你想要打包的模块的数组
entry: {
vendor: ['vue','element-plus']
},
output: {
path: path.resolve(__dirname, '../public/vendor'), // 打包后文件输出的位置,要在静态资源里面避免被打包转义
filename: '[name].dll.js',
library: 'vendor_library'
// 这里需要和webpack.DllPlugin中的`name: '[name]_library',`保持一致。
},
plugins: [
new CleanWebpackPlugin(),
new webpack.DllPlugin({
path: path.resolve(__dirname, '[name]-manifest.json'),
name: 'vendor_library',
context: __dirname
})
]
};
同时在packake.json里面配置dll的命令
"scripts":{
"dll": "webpack --config build/webpack.dll.config.js",
}
最后在webpack.prod.js 加入配置项
···
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./vendor-manifest.json')
}),
···
随后执行命令npm run dll 在public/vendor会出现一个vendor.dll.js文件,我们需要在html文件引入这个文件.
配置完毕,这样我们在不需要更新第三方包的时候可以不用执行npm run dll,然后直接执行npm run build/dev的时候就会发现构建速度有所提高。
使用webpack-bundle-analyzer,启动项目后会打开一个展示各个包的大小。从图中可以看出来,es6.promise.js这个包
webpack身为前端必备的一项技能,各位在学会基础的配置之后,千万别忘了因地制宜,看看哪些插件更适合自己的项目哦
原文链接:
https://juejin.cn/post/7277490138518159379