Webpack5.0手把手教你丛零搭建
前言
开发过程中一直冲锋陷阵于业务,对工程化的项目一直是享受其中,对webpack架构了解甚少,遇到工程化问题一头雾水,所以针对现有webpack5.70.0版本,丛零到高级进阶篇统统学习了一遍,本篇主要对学习过程做输出。
了解webpack
- 安装
npm install webpack webpack-cli webpack-dev-server -D
编译:npx webpack 启动 npx webpack server
webpack是什么
打包器: webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler) 。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph) ,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle
构建工具:主要理解一下这个前端资源是哪些资源。这些前端资源就是浏览器不认识的 web 资源, 比如 sass、less、ts,包括 js 里的高级语法。这些资源要能够在浏览器中正常工作,必须一一经过编译处理。而 webpack 就是可以集成这些编译工具的一个总的构建工具。
为什么要用webpack
回答这个问题,可以和还没有 Webpack、没有构建工具时对比一下,就能明显地感觉出来了。这里就来列举一下不使用构建工具时的痛点。
web 开发时调用后端接口跨域,需要其他工具代理或者其他方式规避。 改动代码后要手动刷新浏览器,如果做了缓存还需要清缓存刷新。 因为 js 和 css 的兼容性问题,很多新语法学习了却不能使用,无论是开发效率和个人成长都受影响。 打包问题。需要使用额外的平台如 jekins 打包,自己编写打包脚本,对各个环节如压缩图片,打包 js、打包 css 都要一一处理。 ...... 而这些问题,Webpack 都提供了解决方案,你只需要做一些简单的配置就可以上手使用了。当然,Webpack 做的还不止这些,下面就来一一介绍。
webpack核心配置
- entry(入口)引入根目录文件路径
entry: "./src/index.js",
// array方式:多入口,所有入口文件最终只会形成一个chunk,输出出去只有一个bundle文件
entry: ["./src/index.js", "./src/test.js"],
// object:多入口,有几个入口文件就形成几个chunk,输出几个bundle文件。此时chunk的名称就是对象key值
entry:{
index:"./src/index.js",
test:"./src/test.js",
}
- output(输出)属性告诉 webpack 在哪里输出它所创建的 bundles以及如何命名这些文件,默认值为
./dist
// contenthash读取缓存,并能发现更新过的文件并编译成最新到
filename: 'script/[name].[contenthash].js',
path: path.resolve(__dirname, './dist'),
clean: true, //清除每一次打包前的dist。相当于clean-webpack-plugin插件的作 用,webpack5新增。
assetModuleFilename: 'images/test[contenthash][ext]',
publicPath: 'http://localhost:8080/' //配置域名
},
拆分生产环境与开发环境配置
- 公共路径
publicPath 配置选项在各种场景中都非常有用。你可以通过它来指定应用程序中所有资源都基础路径。
-基于环境设置
在开发环境中,我们通常有一个assets/文件,它与索引页面位于同一级别。这没太大问题,但是如果我们将所有静态资源托管至cdn,然后想在生产环境中想要解决这个问题 可以直接使用一个environment variable(环境变量)。假设我们有一个变量ASSET_PATH: cosnt ASSET_PATH = process.env.ASSET_PATH||'/‘; 设置生产环境下打包完成后压缩代码 const TerserPlugin = require('terser-webpack-plugin'); 安装 npm install terser-webpack-plugin -D
- loader loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
rules: [
{ //用于配置配文件
test: /\.png$/,
type: 'asset/resource',
generator: { //contenthash动态hash名称
filename: 'images/test[contenthash][ext]'
}
},
{
test: /\.svg$/,
type: 'asset/inline',
},
{
test: /\.txt$/,
type: 'asset/source'
},
{
test: /\.jpeg$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 * 1024
}
}
},
{//创建style标签,将js中的样式资源(就是css-loader转化成的字符串)拿过来,添加到页面head标签生效
//执行顺序为从左向右执行,从下至上执行
test: /\.(css|less)$/,
use: [MinCssExtractPlugin.loader, 'css-loader', 'less-loader']
},
{
test: /\.(woff|wof2|eot|ttf|otf)$/,
type: 'asset/resource',
},
{
test: /\.(csv|tsv)$/,
use: 'csv-loader',
},
{
test: /\.xml$/,
use: 'xml-loader',
},
{
test: /\.toml$/,
type: 'json',
parser: {
parse: toml.parse
}
},
{
test: /\.yaml$/,
type: 'json',
parser: {
parse: yaml.parse
}
},
{
test: /\.json5$/,
type: 'json',
parser: {
parse: json5.parse
}
},
{
test: /\.js$/,
//打包去除node_modules
exclude: /node_modules/,
use: {
//js兼容处理 babel
loader: 'babel-loader',
options: {
// 预设:指示babel做怎么样的兼容处理
presets: ['@babel/preset-env'],
plugins: [
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
]
},
Webpack5.0新增资源模块(asset module),它是一种模块类型,允许使用资源文件(字体,图标等)而无需 配置额外 loader。支持以下四个配置 asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。 asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。 asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。 asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资 源体积限制实现。
Webpack4使用file-loader实现 { test: /.(eot|svg|ttf|woff|)/, type: "asset/resource", generator: { // 输出文件位置以及文件名 filename: "fonts/[name][ext]" }, }, // Webpack4使用url-loader实现 { //处理图片资源 test: /.(jpg|png|gif|)/, type: "asset", generator: { // 输出文件位置以及文件名 filename: "images/[name][ext]" }, parser: { dataUrlCondition: { maxSize: 10 * 1024 //超过10kb不转base64 } } }, ],
-
npm install -D babel-loader @babel/preset-env
-
webpack是做js文件的打包,而并不能做js代码的转换,例如es6转换es5, 高版本浏览器可解析,低版本就会报错
-
babel-loader:在webpack里应用Babel解析es6的桥梁,兼容低版本浏览器
-
@Babel/core: babel核心模块
-
@Babel/preset-env: babel预设,一组Babel插件的集合 在webpack 配置中,需要将将babel-loader添加到module列表中如下:
{ test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], plugins: [ [ '@babel/plugin-transform-runtime' ] ] } } }
在代码当中用到了async await es6语法,需要安装regeneratorRuntime插件解决转换
-
plugin
loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。[插件接口(www.webpackjs.com/api/plugins… 想要使用一个插件,你只需要
require()它,然后把它添加到plugins数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用new操作符来创建它的一个实例。
//HtmlWebpackPlugin帮助你创建html文件,并自动引入打包输出的bundles文件。支持html压缩。
const HtmlWebpackPlugin = require("html-webpack-plugin");
//该插件将CSS提取到单独的文件中。它会为每个chunk创造一个css文件。需配合loader一起使用
const MinCssExtractPlugin = require('mini-css-extract-plugin');
//这个插件也可以用来压缩 css 文件。和 optimize-css-assets-webpack-plugin 加 cssnano 的效果是一样的。
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
//压缩js代码。
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
从webpack5开始,webpack内置了该功能,只要在ouput中配置clear为true即可
plugins: [
new HtmlWebpackPlugin({
template: './index.html', //要产出的根目录文件
filename: 'app.html', //产出文件的名字
inject: 'body',// 把生成的script标签放到body里
}),
new MinCssExtractPlugin({
filename: 'styles/[contenthash].css',
})
],
-
mode
development:(开发)会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。
production:(生产)会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin 。
DefinePlugin:定义全局变量process.env.NODE_ENV,区分程序运行状态。 FlagDependencyUsagePlugin:标记没有用到的依赖。 FlagIncludedChunksPlugin:标记chunks,防止chunks多次加载。 ModuleConcatenationPlugin:作用域提升(scope hosting),预编译功能,提升或者预编译所有模块到一个闭包中,提升代码在浏览器中的执行速度。 NoEmitOnErrorsPlugin:防止程序报错,就算有错误也继续编译。
-
devserver
devServer: {
// 运行代码的目录
contentBase: path.resolve(__dirname, "build"),
// 为每个静态文件开启gzip压缩
compress: true,
host: true,//热更新
port: 8000,
open: true,
// 自动打开浏览器
hot: true,
//如果我们的应用是一个SPA(但页面应用),当路由到/some时(可以直接只地址栏里输入)会发现此时刷新页面后,控制台会报错。
historyApiFallback: true,
liveReload: true,//热替换
headers: {
'X-Access-Token': 'lktesttoken'
}
//开启HMR功能
// 设置代理
proxy: { // 一旦devServer(5000端口)接收到/api/xxx的请求,就会用devServer起的服务把请求转发到另外一个服务器(3000) // 以此来解决开发中的跨域问题
api: { target: "htttp://localhost:3000", // 发送请求时,请求路径重写:将/api/xxx --> /xxx (去掉/api)
pathRewrite: { "^api": "", },
},
},
}
//生产环境下
optimization: {
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin()
],
splitChunks: { //用于优化,可以帮助公共代码的抽离,比如工具库,loadsh,ahooks等
cacheGroups: { // 引用第三方文件用于缓存浏览器(由于这种类库不频繁更新,可以提高我们打开速度,以及节省我们的网络流量)
vendor: {
test: /[//\]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
webpack 打包优化
devtool
- eval:每个module会封装到eval里包裹起来执行,并且会在末尾追加注释//@sourceURL。
- source-map: 生成一个source-map
- hidden-source-map 生成一个source-map一样,但不会在bundle末尾添加注释。
- inline-source-map 生成DataUrl形式source-map文件。
- eval-source-map: 每个module都会从eval来执行,并且生成一个DataUrl形式的sourceMap。
- chep-source-map:生成一个没有列信息(column-mappings)的sourceMap文件,不包含loader的sourcemap(譬如Babloaderel的sourcemap)
- cheap-module-source-map:生成一个没有列信息(column-mappings)的source-Map文件,同时的soucemap也被简化为只包含对应行的。
- 要注意的是,生产环境我们一般不会开启source-map功能,主要有两个原因:
1、通过bundle和sourcemap文件,可以反编译出源码---也就是说,线上产物有soucemap文件的话,那就意味有暴露源码的风险。 2、我么观察到,sourcemap文件的体积相对比较巨大,这跟我们生产环境的追求不同(生产环境追求更小更轻量的bundle)。
webpack 配置文件抽离公共配置
- 安装 npm install webpack-merge -D //引用merge pai
- const { merge } = require('webpack-merge');
//抽离出开发与生产公共部分
- const commonConfig = require('./webpack.config.common');
//只编写生产环境的配置
- const productionConfig = require('./webpack.config.prod');
//只编写开发环境下的配置
- const developmentConfig = require('./webpack.config.dev');
switch (true) {
//根据编译环境 产出不同环境的产物
case env.development:
return merge(commonConfig, developmentConfig)
case env.production:
return merge(commonConfig, productionConfig)
default:
return new Error('没有匹配到config')
}
}
代码分离 减小入口文件的大小,增加编译速度
{
入口起点(使用entry配置手动分离代码)
代码分离 防止重复(使用entry dependenvies或者SplitChunksPlugin 去重和分离代码)
动态导入(通过模块的内联函数调用来分离代码)
}
防止代码重复(优化)
- 入口依赖
配置 dependOn Option选项,这样可以在多个chunk之间共享模块
{
import: './src/index.js',
dependOn: 'shared'
},
another: {
import: './src/another-module.js',
dependOn: 'shared'
},
shared: 'lodash'
缓存
- 缓存自己业务内的代码
- webpack编译加载时候能够把文件缓存到客户端,以及发现新的文件变化时候,会请求到新的文件。
- 输出的文件名字: filename: '[name].[contenthash].js'
我们可以通过替换output.filename中的substitutions设置,来定义输出文件的名称。webpack提供来一种使用名称为substion(可替换模板字符串)的方式,通过带括号
- 缓存第三方库的代码,lodash\ahooks...
将第三方库的(libray)提到单独的vendor chun文件中,是比较推荐的方法,这是因为,他们很少像本地代码频繁修改,通过实现以上步骤,利用client缓存机制,命中缓存来消除请求 optimization.splitChunks添加cacheGroups参数构建.
持续更新进阶篇。。。 不足之处希望在线提出批评与指导。吸取广大同胞建议,做到最好。。。