前端工程化-webpack

94 阅读5分钟

webpack篇

前言

为什么要使用打包工具

随着前端工程抽象复杂度的提高,前端开发框架盛行,前端项目代码抽象度提高,原始浏览器兼容版本(提供的web api)可能不支持新的语法,如ES6、less、甚至框架语法-vue、react语法等,具备编译转化功能的工程打包工具应运而生,webpack为杰出代表。

打包工具能实现哪些功能

压缩代码、兼容处理、编译转换提升代码性能,且一些适配型打包工具,往往还提供插件,用于作用在打包编译的过程中,让打包过程更可控。

常见打包工具

gulp blog.csdn.net/weixin_5809…
rollup
webpack
三者优缺点分析: www.jianshu.com/p/cea946fa3…
概括为webpack应用最为广泛,适配性强,意味着专用的便利性差。因此其他如gulp、rollup

webpack 基本使用

编译打包之前源代码为.vue、.less、es6等高版本语言,以一个或多个文件作为打包入口,将项目所有文件编译处理为一个或者多个文件
Webpack 是基于 Node.js 运行的,所以采用 Common.js 模块化规范
webpack官网:https://webpack.docschina.org/

五大核心配置

webpack.config.js配置文件
module.exports = {
  // 入口
  entry: "",
  // 输出
  output: {},
  // 加载器
  module: {
	rules: [],
  },
  // 插件
  plugins: [],
  // 模式
  mode: "",
};

资源处理:处理css资源(.less/.sass)、处理js(es6)、处理图片资源(png/jpg)、处理其他资源(.ttf/.swf)、处理框架资源(.vue)

// 封装通用样式loader函数
const getStyleLoaders = (pre) => {
  return [
	isProduction ? MiniCssExtractPlugin.loader : "vue-style-loader",
	"css-loader",
	{
	  // 处理css兼容性问题
	  // 配合package.json中browserslist来指定兼容性
	  loader: "postcss-loader",
	  options: {
		postcssOptions: {
		  plugins: ["postcss-preset-env"],
		},
	  },
	},
	pre && {
	  loader: pre,
	  options:
		pre === "sass-loader"
		  ? {
			  additionalData: `@use "@/styles/element/index.scss" as *;`,
			}
		  : {},
	},
  ].filter(Boolean);
};

module: {
	rules: [
		// 处理css
		{
			test: /\.css$/,
			use: getStyleLoaders(),
		},
		{
			test: /\.less$/,
			use: getStyleLoaders("less-loader"),
		},
		{
			test: /\.s[ac]ss$/,
			use: getStyleLoaders("sass-loader"),
		},
		{
			test: /\.styl$/,
			use: getStyleLoaders("stylus-loader"),
		},
		// 处理图片
		{
			test: /\.(jpe?g|png|gif|webp|svg)$/,
			type: "asset",
			parser: {
			  dataUrlCondition: {
				maxSize: 10 * 1024,
			  },
			},
		},
		// 处理其他资源
		{
			test: /\.(woff2?|ttf)$/,
			type: "asset/resource",
		},
		// 处理js
		{
			test: /\.js$/,
			include: path.resolve(__dirname, "../src"),
			loader: "babel-loader",
			options: {
			  cacheDirectory: true,
			  cacheCompression: false,
			},
		},
		{
			test: /\.vue$/,
			loader: "vue-loader",
			options: {
			  // 开启缓存
			  cacheDirectory: path.resolve(__dirname, "../node_modules/.cache/vue-loader"),
			},
		},
	],
},

总结 loader使用

设置include、exclude,对处理文件进行筛选,用于特异化处理,并不是每个文件都需要处理(如node_module中的文件不需要处理)
test适配文件类型
loader为指定load名称,需要先下载,如sass-loader(npm install sass-loader sass webpack --save-dev)依赖要下全,具体参考官网
type非代码的资源文件指定类型
include、exclude指定处理包含哪些文件不包含哪些,结果接受参数为ObjectArrayFunction
sourceMap,指定当报错时报错到源文件位置,便于开发时定位问题
additionData,对处理文件添加内容,如公共变量等
options(重要) loader的配置

	如sassLoader:
	  {
		loader: 'sass-loader',
		options: {
		  sassOptions: (loaderContext) => {
			// 有关可用属性的更多信息 https://webpack.js.org/api/loaders/
			const { resourcePath, rootContext } = loaderContext;
			const relativePath = path.relative(rootContext, resourcePath);

			if (relativePath === 'styles/foo.scss') {
			  return {
				includePaths: ['absolute/path/c', 'absolute/path/d'],
			  };
			}

			return {
			  includePaths: ['absolute/path/a', 'absolute/path/b'],
			};
		  },
		},
	  },

常用loader对应常用文件类型css(css-loader/style-loader/less-loader/postcss-loader)、js(babel-loader/ts-loader)、其他资源

总结 plugin使用

常用pluginHtmlWebpackPluginESLintWebpackPlugin// plugin的配置
new ESLintPlugin({
  // 检测哪些文件
  context: path.resolve(__dirname, "../src"),
  exclude: "node_modules", // 默认值
  cache: true, // 开启缓存
  cacheLocation: path.resolve(
    __dirname,
    "../node_modules/.cache/eslintcache"
  ),
  threads, // 开启多进程和设置进程数量
}),

css样式处理

提取为单独css文件

默认只使用css的loader,则css被打包到js文件中,这样js文件加载时,会创建style标签,导致页面闪屏。通过link引入css样式文件可以解决这个问题。 就需要用到 const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 并采用loader的形式引入:

  {
    test: /\.s[ac]ss$/,
    use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
  },
  
css兼容处理

使用postcss,配合browserlist(指定兼容浏览器版本)

package.json :"browserslist": ["last 2 version", "> 1%", "not dead"]

      {
        loader: "postcss-loader",
        options: {
          postcssOptions: {
            plugins: [
              "postcss-preset-env", // 能解决大多数样式兼容性问题
            ],
          },
        },
      },
css压缩

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// css压缩 new CssMinimizerPlugin(),

开发模式与生产模式

开发环境常用于编译页面,修改自动更新,需考虑缓存,热更新;生产模式考虑代码运行性能,打包速度,需考虑压缩。

开发模式

启动服务,更新页面

devServer: { host: "localhost", // 启动服务器域名 port: "3000", // 启动服务器端口号 open: true, // 是否自动打开浏览器 },

一些常用配置文件

.eslintrc.js eslint配置,用于代码规范,可以继承vue、react官方的模版;另外的优化点,按需引入用到的如Promise .postcssrc.js 设置css版本兼容,可以直接在package.json或者webpack.config.js中设置 .babel.config.js babel配置

优化处理

提升开发体验

devtool: "cheap-module-source-map", // 配置在浏览器控制台,看到console的行 devtool: "source-map", // 配置在浏览器控制台,看到console的行和列,但编译打包速度慢

提升打包构建速度

热模替换HMR

修改个别模块就编译整个项目,效率低,只编译修改的模块将大大提升效率

module.exports = {
  // 其他省略
  devServer: {
	host: "localhost", // 启动服务器域名
	port: "3000", // 启动服务器端口号
	open: true, // 是否自动打开浏览器
	hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
  },
};

原生js热模替换需配置之后,再判断该模块是否支持 if(module.hot)比较麻烦
实际项目中vue-loader、react-hot-loader已经做了
loader精准匹配OneOf

设置匹配到文件类型之后就不进行别的loader查找

new ESLintWebpackPlugin({
  // 指定检查文件的根目录
  context: path.resolve(__dirname, "../src"),
  // exclude: /node_modules/, // 默认值,排除node_modules代码不编译
  include: path.resolve(__dirname, "../src"), // 也可以用包含
}),
cache缓存

每次打包编译文件都要经过eslint检查和babel编译,可以缓存cache结果

{
	test: /\.js$/,
	// exclude: /node_modules/, // 排除node_modules代码不编译
	include: path.resolve(__dirname, "../src"), // 也可以用包含
	loader: "babel-loader",
	options: {
		cacheDirectory: true, // 开启babel编译缓存
		cacheCompression: false, // 缓存文件不要压缩
	},
},

new ESLintWebpackPlugin({
  // 指定检查文件的根目录
  context: path.resolve(__dirname, "../src"),
  exclude: "node_modules", // 默认值
  cache: true, // 开启缓存
  // 缓存目录
  cacheLocation: path.resolve(
    __dirname,
    "../node_modules/.cache/.eslintcache"
  ),
}),
开启多进程

继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。

而对 js 文件处理主要就是 eslint 、babel、Terser 三个工具,所以我们要提升它们的运行速度。

npm i thread-loader -D

const threads = os.cpus().length;

在特定loader或plugin中设置开启多线程

{
	test: /\.js$/,
	// exclude: /node_modules/, // 排除node_modules代码不编译
	include: path.resolve(__dirname, "../src"), // 也可以用包含
	use: [
	  {
		loader: "thread-loader", // 开启多进程
		options: {
		  workers: threads, // 数量
		},
	  },
	  {
		loader: "babel-loader",
		options: {
		  cacheDirectory: true, // 开启babel编译缓存
		},
	  },
	],
},

new ESLintWebpackPlugin({
  // 指定检查文件的根目录
  context: path.resolve(__dirname, "../src"),
  exclude: "node_modules", // 默认值
  threads, // 开启多进程
}),

减少代码体积

tree shaking

移除没有用到的js代码,webpack默认开启

babel

babel默认为编译的文件都写入辅助代码,导致代码体积较大

{ loader: "babel-loader", options: { cacheDirectory: true, // 开启babel编译缓存 cacheCompression: false, // 缓存文件不要压缩 plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积 }, }

Image Minimizer

静态图片压缩

优化代码运行性能

代码分割

针对所有js打包到一个文件的情况,导致首屏加载过慢,将代码分包打包,则分化首次加载压力,包含功能:文件分割,按需加载

预加载

代码分割,同时会使用 import 动态导入语法来进行代码按需加载(我们也叫懒加载,比如路由懒加载就是这样实现的)。

但是加载速度还不够好,比如:是用户点击按钮时才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。

我们想在浏览器空闲时间,加载后续需要使用的资源。我们就需要用上 Preload 或 Prefetch 技术。

网络缓存

对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了,速度很快。

core-js

过去我们使用 babel 对 js 代码进行了兼容性处理,其中使用@babel/preset-env 智能预设来处理兼容性问题。

它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理。

所以此时我们 js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。

PWA

开发 Web App 项目,项目一旦处于网络离线情况,就没法访问了。

我们希望给项目提供离线体验。