webpack
核心模块webpack cli模块webpack-cli
查看webpack 命令
js
yarn webpack --version
DEMO2
webpack4版本之后,支持零配置 按照约定将src/index.js 打包到dist/main.js 如果需要自定义,就要添加配置文件
mode目前只有三张模式:development/production/none mode默认是生产模式production,会优化打包结果(代码压缩、
也可通过cli设置mode,开发环境,会优化打包速度,在代码中添加调试过程中需要的辅助[devtool],方便调试:sourceMap
js
yarn webpack --mode development
设置mode为none模式,最原始的打包,不会做额外的处理
js
yarn webpack --mode none
模块Id是数组下标
js
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _hello_md__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
/* harmony import */ var _hello_md__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_hello_md__WEBPACK_IMPORTED_MODULE_0__);
console.log(_hello_md__WEBPACK_IMPORTED_MODULE_0___default.a);
/***/ }),
/* 1 */
/***/ (function(module, exports) {
console.log("hello loader")
/***/ })
/******/ ]);
设置mode为development 入参是map,key是文件路径,即模块id
js
/******/ ({
/***/ "./src/hello.md":
/*!**********************!*\
!*** ./src/hello.md ***!
\**********************/
/*! no static exports found */
/***/ (function(module, exports) {
eval("console.log(\"hello loader\")\n\n//# sourceURL=webpack:///./src/hello.md?");
/***/ }),
/***/ "./src/main.js":
/*!*********************!*\
!*** ./src/main.js ***!
\*********************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _hello_md__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./hello.md */ \"./src/hello.md\");\n/* harmony import */ var _hello_md__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_hello_md__WEBPACK_IMPORTED_MODULE_0__);\n\n\nconsole.log(_hello_md__WEBPACK_IMPORTED_MODULE_0___default.a);\n\n\n//# sourceURL=webpack:///./src/main.js?");
/***/ })
/******/ });
DEMO3--原理解读
设置mode为none模式,方便理解
生成了一个立即执行函数IIFE,工作入口
js
/******/ (function(modules) { // webpackBootstrap --工作入口
/******/ // The module cache
/******/ var installedModules = {}; // 缓存模块
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {// 第一次moduleId为0
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ // modules是立即执行函数传进来的函数数组参数
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/ // Load entry module and return exports
/******/ // 开始调用__webpack_require__函数,初始化调用参数数组的第一个元素,这里才开始加载模块
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/***/ }),
/******/ ]);
立即执行函数是入口,参数是数组,数组中每一个函数 第一执行 webpack_require(0) // moduleId为0
js
/******/ // Load entry module and return exports
/******/ // 开始调用__webpack_require__函数,初始化调用参数数组的第一个元素,这里才开始加载模块
/******/ return __webpack_require__(__webpack_require__.s = 0);
webpack_require 函数实现
js
/******/ function __webpack_require__(moduleId) { // 第一次moduleId为0
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId, // 数组下标为模块id
/******/ l: false, // 表示该模块是否已加载
/******/ exports: {} // 导出模块
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
加载模块
js
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
执行模块里面的函数,使用严格模式,调用__webpack_require__.r函数
js
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
// 这里调用__webpack_require__,参数是1,该模块用来加载依赖的第一个模块
/* harmony import */ var _head_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
const head = Object(_head_js__WEBPACK_IMPORTED_MODULE_0__["default"])();
document.body.append(head);
/***/ }),
调用__webpack_require__.r函数,添加标识,用来标识是__esModule
js
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
接下该模块内部加载依赖的模块,它依赖第一个模块,(从这里可以看出,不能自己引用自己
js
/* harmony import */ var _head_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
开始调用该模块
js
const head = Object(_head_js__WEBPACK_IMPORTED_MODULE_0__["default"])();
webpackBootstrap作用 处理各模块依赖关系,保持原有状态
DEM4-资源文件打包
loader是webpack实现前端模块化的核心 webpack默认把所有的文件当js文件处理,内部加载器只认识js文件 不能直接处理css文件,需要使用css-loader、style-loader 多个loader,是从后往前执行
js
module: {
rules: [
{
test: /.css$/,
use: ['style-loader', 'css-loader']
}
]
}
css-loader的作用就是把css文件转换成js模块
js
var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default()(function(i){return i[1]});
// Module
___CSS_LOADER_EXPORT___.push([module.i, "body {\n background-color: blue;\n}\n", ""]);
// Exports
/* harmony default export */ __webpack_exports__["default"] = (___CSS_LOADER_EXPORT___);
style-loader作用就是把css-loader转换的结果通过标签的形式追加到页面上
loader是webpack实现前端模块化的核心,通过不同的loader,可以加载任何资源文件
file-loader
文件加载器,把资源文件拷贝到打包目录
url-loader
Data Urls是特殊的url 协议,它可以用来直接表示一个文件,这样就不会发送 一般的url是需要服务器上有一个文件,通过请求这个地址,得到这个文件 格式 data:[][;base64], data: -- 协议 -- 媒体类型和编码 -- 文件内容 使用这样的协议,就不会发送http请求,浏览器能解析,输入到浏览器地址即可
eg: data:text/html;charset=UTF-8,
html content
输入到浏览器地址,浏览器能解析这是一个html
data:image/png;base64,ivshdsdbhdjbhbudhjdhvb....
webpack中可以使用url-loader来处理这种data url
js
{
test: /.png$/,
use: 'url-loader'
}
这样dist目录就不会有图片文件了,导出的data url 的base64编码
Data URLS使用场景 本地小文件使用Data URL,可以减少请求次数 本地大文件单独提取存放,提高加载速度
js
{
test: /.png$/,
use: {
loader: 'url-loader',
options: {
// 10 KB ,对10KB以内的png文件会使用data urls来转换成base64,
// 超出还是使用file-loader,因此file-loader包不能remove
limit: 10 * 1024
}
}
}
Webpack 常用loader分类
编译转换类,作用就是把资源模块转换成js代码 比如foo.css文件,通过css-loader,打包到bundle.js中
文件操作类,作用就是把加载的资源文件拷贝到打包目录,导出文件的访问路径
代码检查类,作用就是校验我们的代码(通过/不通过),统一代码风格,提高代码质量, 这种加载器不会修改生产环境的代码,比如eslint-loader
webpack 默认都支持esModule/AMD/commonJS/CMD,无需配置额外的loader
需要配置library字段
demo7-webpack-es2015
webpack完成模块打包工作,默认会处理es module中import/export, 无需额外的loader来处理es module中import/export 但是webpack并不会编译转换es6其他的特性 比如:箭头函数是不会转换成普通函数
js
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = (() => {
const ele = document.createElement('h2');
ele.textContent = 'Hello world';
ele.addEventListener('click', () => {
alert('hello webpack');
});
return ele;
});
如果需要对es6特性转换,就需要安装编译转换类的loader babel-loader,依赖babel的核心模块@babel/core, 还需要用于编译转换的插件集合@babel/preset-env,
js
{
test: /.js$/,
use: 'babel-loader'
}
babel-loader是一个代码转换平台,真正转换的是babel插件,因此要配置插件
js
{
test: /.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
webpack只是打包工具,不会处理es6或者更高版本的新特性, 加载器可以用来编译转换代码
webpack 模块打包方式
1.遵循ES Modules 标准的import 声明 2.遵循CommonJS标准的require函数
js
// CommonJs中的require导入ES Module,对于ES Module中默认导出,需要对导入的对象的default属性获取
const createHeading = require('./heading.js').default;
3.遵循 AMD 标准的define函数和require函数
webpack兼容多种模块化标准,尽量避免使用在一个项目中混合使用,混用会降低可维护性
Loader 加载的非js也会触发资源加载 1.样式代码中的 @import 指令 和 url 函数 2.HTTML代码中图片标签的src属性
html中也会有引用资源文件
js
// foot.html
<footer>
<img src="better.png" alt="bet">
</footer>
js
import footHtml from './foot.html';
document.write(footHtml);
webpack默认只认识js文件,需要html-loader加载html模块
html-loader默认只会处理img标签的src属性,如果需要处理其他标签属性
js
{
test: /.html$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src', 'a:href']
}
}
}
webpack 核心工作原理(webpack只识别js模块,其他资源都是通过loader来转换成js代码)
项目中一般有各种类型的资源文件js/css/png/json/jpg/scss等, webpack首先通过webpack配置找到打包入口文件 webpack根据代码中的import / require 语句推断解析依赖的模块 解析每个资源模块对应的依赖,建立项目中所有文件的依赖关系的树, webpack会递归依赖树,找到每个节点对应的资源文件,通过配置文件的rules属性 找到对应文件的加载器来加载该模块,将加载结果放到bundle.js中,实现整个项目打包, Loader机制是webpack的核心,可以用来处理各种资源文件,如果没有Loader,webpack 只是js代码合并工具
Loader 核心工作原理 --demo8-手写loader
loader工作原理,就是将资源文件从输入到输出的转换,最终转换成js代码 webpack中对于同一个资源可以一次使用多个Loader 比如:css-loader -> style-loader
js
{
test: /.md$/,
use: [
'html-loader',
'markdown-loader', //
]
}
资源文件 --> markdown-loader接收资源文件内容,转换成html字符串 --> html-loader将md文件转换成js代码(比如 return module.exports = ${JSON.stringify(html)}
) A资源类型的Loader加载A资源, Loader的入参就是A资源的内容,最后一个Loader必须返回js代码,否则webpack 不能识别
webpack插件
目的是增强Webpack自动化能力 loader是专注实现各种资源模块加载(非js资源文件转成js模块),也可以不兼容js模块转成兼容的js模块的loader
Plugin是用来解决除了资源加载以外的其他的自动化工作 eg: 1.每次重新打包,清除上一次打包结果的 dist 目录 2.拷贝不需要参与打包的静态文件至输出目录 3.压缩打包的输出代码
Webpack + Plugin 实现了大多前端工程化工作
常用插件
*clean-webpack-plugin // 自动清除输出目录插件
webpack打包都是覆盖打包目录(dist目录内的文件),同名文件会被覆盖,对于未重名的文件就会一直积累在dist目录 因此,需要在每次打包前清理dist目录,clean-webpack-plugin很好满足了这次需求
js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const config = {
...
plugins: [
new CleanWebpackPlugin()
]
}
*html-webpack-plugin // 自动生成使用bundle.js的html
2个问题 1.发布时,需要发布dist目录的所有打包结果和根目录下html文件 2.webpack打包的结果bundle.js,需要手动引入到html中,上线需要确保script标签引用路径是正确的 一旦,配置发生变化,打包文件名发生变化,需要手动去修改html中script标签引用路径
导致这个问题的根因是硬编码产生的
需要通过webpack输出Html文件到dist目录,自动注入引用打包结果bundle.js文件到html
配合html-webpack-plugin定制html模板
1.定制title; 2.增加标签; 3.增加参数,html通过<%= htmlWebpackPlugin.options.title %>; 4.指定html模板; 5.多实例,每个HtmlWebpackPlugin实例对象生成都会生成一个html,多个生成多个html,多页面应用
js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const config = {
...
plugins: [
new HtmlWebpackPlugin({
title: 'webpack plugin title',
meta: {
viewport: 'width=device-width'
},
template: './index.html' // 指定html模板
}), // script标签的路径是output中${publicPath}/${filename}
// index_2.html
new HtmlWebpackPlugin({
filename: 'index_2.html';
})
]
}
copy-webpack-plugin 项目中经常有不需要参与构建的静态文件,比如html模板引用的本地favicon.icon图标文件等,这类文件不需要参与打包
最常用的插件 1.clean-webpack-plugin 2.html-webpack-plugin 3.copy-webpack-plugin
Plugin相比于Loader,Plugin拥有更宽的能力范围
Plugin是通过webpack的钩子机制实现
webpack中Plugin必须是一个函数或者是一个包含apply方法的对象
js
// 通过钩子机制实现自定义插件,
// 实现删除bundle.js中的注释
class MyPlugin {
apply(compiler) {
console.log('MyPlugin 启动');
compiler.hooks.emit.tap('MyPlugin', (compilation) => {
// compilation => 此次打包的上下文
// compilation.assets => 打包的之后的文件
for(const name in compilation.assets) {
// console.log('name', name);
// console.log('name', compilation.assets[name].source());
if (name.endsWith('.js')) {
const contents = compilation.assets[name].source();
const withoutComments = contents.replace(/\/\*\*+\*\//g, '');
compilation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length // webpack内部要求的,需要返回文件大小
}
}
}
})
}
}
Plugin插件:通过在webpack打包的生命周期的钩子中挂载函数实现扩展
Webpack 开发体验
编写源码 -> Webpack打包 -> 运行应用 -> 刷新浏览器
优化 1.http server 运行 2.自动编译 + 自动刷新 3.source map支持
watch模式:监听文件变化,自动重新打包
js
yarn webpack --watch // 监视模式运行打包,需要手动刷新浏览器
编译之后,自动刷新浏览器 browser-sync工具可以实现
js
npm i browser-sync --dev // 安装browser-sync工具
// 使用browser-syncy启动http server
browser-sync dist --file "**/*"
优化: 1.自动编译,自动刷新
缺点: 1、操作麻烦,同时需要使用两个工具 2.效率降低,webpack会不断将文件写入磁盘
webpack-dev-server
集成了上述功能 1.提供了用于开发的http server 2.集成了自动编译 和自动刷新浏览器等功能
特性:使用webpack-dev-server开发,为提高工作效率,并不会把打包文件写入dist目录(磁盘中), 而是把打包结果暂时存放在内存当中,内部的http server从内存中读取出来,大大减少磁盘读写操作 提高构建效率
webpack dev server 默认只会将开发构建文件作为开发服务器的资源文件,如果serve也需要其他资源文件,需要增加配置
js
devServer: {
contentBase: './public' // 指定额外的资源路径
},
配置代理服务器 比如生产环境api www.prod.com/api
开发环境 http://localhost:8080/index.html
CORS:跨域资源共享,需要api支持跨域
前后端同源部署(域名、协议、端口一致),没有必要开启cors
问题:开发阶段接口跨域问题s
API服务器 开发服务器
markdown
浏览器
就是将接口服务代理到本地开发服务器地址
js
proxy: {
'/api': {
// http://localhost:8080/api/users -> https://api.github.com/api/users(不对,多了api)
target: 'https://api.github.com',
// https://api.github.com/api/users -> https://api.github.com/users
pathRewrite: {
'^/api': '' //将/api替换为空
},
// 不能使用localhost:8080作为请求Github的主机名,原因去查http host
changeOrigin: true
}
}
Source Map(源代码地图) 缘由:webpack打包之后运行代码与源代码之间差异很大,导致调试应用、错误信息无法快速定位 调试和报错都是编译打包之后的代码运行的
Source Map作用就是映射编译打包代码和源代码 编译打包的代码通过SourceMap文件逆向解析得到源代码
SourceMap文件是一种json格式的.map后缀的文件,以jquery-3.4.1.js为例
js
{
"version": 3, // source map使用的标准的版本,
"sources": ["jquery-3.4.1.js"], // 源代码的文件名称,可能是多个文件,编译打包成一个文件,因此是数组
"names": [ // 表示源代码使用的一些成员名称,开发阶段,会将有意义的变量名压缩替换成简短的字符
"global",
"factory",
"module",
"exports",
"document",
"w",
...
],
"mappings": ";CAaA,SAAYA..." // 核心属性,base64-ZRQ编码,表示转换后的字符和转换前字符的对应关系
}
source map文件只是用来调试和定位错误的,对生产环境没有声明意义
使用source map格式(以jquery-3.4.1.js为例)
js
... // 打包之后的代码
//# sourceMappingURL = jquery.3.4.1.min.map
webpack 配置 source map
devtool配置source map 总共可以支持12种,每种方式的效率和效果各不相同 eval eval是js的一个函数
js
eval('console.log(123)'); // 运行在临时的虚拟机中 VMxx:1
eval('console.log(123) //# sourceURL=./foo/bar.js'); // 指定运行的路径 ./foo/bar.js(其实还是在虚拟机中) ,只是一个标识
使用devtool设置eval模式,打包后模块代码在eval中,在eval函数内部指定文件位置 //# sourceURL=webpack:///./src/main.js?, 就能定位到错位打包后模块文件
js
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _head_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);\n\n\nconst head = Object(_head_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])();\n\nconsole.log1(1234567)\n\ndocument.body.append(head);\n\n\n//# sourceURL=webpack:///./src/main.js?");
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (() => {\n const ele = document.createElement('h2');\n\n ele.textContent = 'Hello world';\n ele.addEventListener('click', () => {\n alert('hello webpack');\n });\n return ele;\n});\n\n//# sourceURL=webpack:///./src/head.js?");
/***/ })
/******/ ]);
在main模块中设置错误信息,错误定位信息 main.js:7 Uncaught TypeError: console.log1 is not a function at eval (main.js:7) at Module. (bundle.js:92) at webpack_require (bundle.js:20) at bundle.js:84 at bundle.js:87
js
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _head_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
const head = Object(_head_js__WEBPACK_IMPORTED_MODULE_0__["default"])();
console.log1(1234567)
document.body.append(head);
这种模式不会生成source map文件,构建速度是最快的,不能定位到源代码的行列信息,只能定位到文件路径名称
demo12
eval-source-map:相比eval生成sourcemap,不仅可以定位到文件名称,还可以定位到编译之后的代码行列信息
cheap-eval-source-map:生成了sourcemap,仅可以编译之后的代码定位到行,没有列
cheap-module-eval-source-map:生成了sourcemap,仅可以源代码代码定位到行,没有列
js
'eval',
'cheap-eval-source-map',
'eval-source-map',
'cheap-source-map',
'cheap-module-source-map',
'inline-cheap-source-map',
'inline-cheap-module-source-map',
'source-map',
'inline-source-map', // sourcemap没有单独文件,sourcemap在打包之后代码最后一行
'hidden-source-map', // 生成了source map 文件,但是没有引用到源代码中,因此在开发环境中看不到效果
// 第三包生成source map 但不引用它们,出现问题再引用
'nosources-source-map' // 生成了source map 文件,但是定位信息点进去看不到源代码
...其他模式就是将这几个模式组合
eval - 是否使用eval执行模块代码 cheap - source map 是否包含行信息 module - 是否能够得到Loader处理之前的源代码
选择sourcemap经验
开发环境:cheap-module-eval-source-map // 定位到行、文件模块 代码每行不超过80字符 代码经过loader转换后差异较大 首次打包速度无要求,重写打包相对较快
生产环境:none,不生成sourcemap,因为source map会暴露代码到生产环境, 可以适当选择nosources-source-map
自动刷新问题 自动刷新导致页面状态丢失
期望页面不刷新的前提下,模块也可以及时更新
HMR Hot Module Replacement
模块热替换/热更新 应用程序运行过程中实时替换某个模块,应用运行状态不受影响
自动刷新导致页面状态丢失,热替换只将修改的模块实时替换至应用中
集成在webpack-dev-server cli命令
js
webpack-dev-server --hot // 开发HMR
webpack插件new webpack.HotModuleReplacementPlugin()也开启HMR
webpack中的HMR需要手动处理模块热替换
样式文件的热更新开箱即用:样式文件是通过loader处理,style-laoder自动处理样式文件的热更新
样式文件更新后会直接替换之前的文件,会覆盖之前的样式,从而实现样式文件热更新
js模块是没有规律,导出的可能是对象、函数、字符串等,对导出的成员的使用也是不同,所以webpack面对这些毫无规律的js模块,webpack无法处理你更新的模块,无法实现所有情况的js模块的热更新方案
框架下的开发,每种文件是有规律, 通过脚手架创建的项目内部都集成了HMR方案
我们需要手动处理JS模块更新后的热替换 new webpack.HotModuleReplacementPlugin(), 开启插件才能使用HMR API HMR API处理热替换
js模块的热替换
js
import createHead from './head';
const head = createHead();
document.body.append(head);
console.log(head);
let lastHead = head;
// HMR API处理./head模块热更新
module.hot.accept('./head', () => {
// console.log('head模块更新了,需要这里手动处理热替换逻辑12');
// console.log(head);
const value = lastHead.innerHTML;
document.body.remove(lastHead);
const newHead = createHead();
newHead.innerHTML = value;
document.body.append(newHead);
lastHead = newHead;
})
// 图片的热替换
js
module.hot.accept('./test', () => {
img.src = png;
});
HMR注意事项 1.处理HMR的代码报错会导致自动刷新,导致错误不易被发现
配置hotOnly不会自动刷新
- 没有启用HMR的情况,HMR API 报错
使用HMR API
JS
if (module.hot) {
// 处理热替换
}
处理热替换的代码, 开发环境打包会去掉热替换代码
js
if (false) {
}
生产环境,压缩过后会直接删掉
生产环境优化
sourcemap、HMR额外的代码提高开发效率,对生产环境没什么作用
生产环境注重运行效率 开发环境注重开发效率
mode(模式)来为不同的工作环境创建不同的配置
方式 1.配置文件根据不同导出不同配置 --小项目 2.一个环境对应一个配置文件 --大项目 使用命令--config来使用配置文件
js
yarn webpack --config webpack.prod.js
webpack.config.js也可以导出一个函数,接收两个参数 env是运行cli传递的环境名参数,argv是运行cli传递的所有参数
js
module.exports = (env, argv) => {
const config = {
mode: "development", // 设置打包模式:开发环境development,生产环境production,none
entry: './src/main.js', // 设置打包入库文件,如果是相对路径,./不能省略
output: {
filename: 'bundle.js', // 指定打包输出文件名称
path: path.join(__dirname, 'dist'), // 必须为绝对路径,不如输入output,会报错
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'webpack plugin title',
meta: {
viewport: 'width=device-width'
},
template: './index.html' // 启用模板html
}), // script标签的路径是output中${publicPath}/${filename}
// 生成index_2.html
new HtmlWebpackPlugin({
filename: 'index_2.html'
}),
// 需要拷贝的文件或者文件目录的数组
new CopyWebpackPlugin([
// 'public/**',支持通配符、文件目录、文件的相对路径
])
]
};
if (env === 'production') {
config.mode = 'production';
config.devtool = false;
config.plugins = [
...config.plugins,
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['plugin'])
]
}
}
DefinePlugin
为代码注入全局成员
js
plugins: [
new webpack.DefinePlugin({
//这里value需要代码片段,因此打包之后变成console.log(https://api.com);
// API_BASE: 'https://api.com',
API_BASE: '"https://api.com"',
API_BASE: JSON.stringify('https://api.com'),
})
]
Tree Shaking(摇树)
摇掉代码中未引用部分(dead-code) 生产环境自动启用
js
optimization: {
usedExports: true, // 开启tree shaking,不导出未使用的模块,打包代码还在
minimize: true, // 压缩代码,去除未使用的代码
}
concatenateModules
尽可能将所有模块合并输出到一个函数 既提升了运行效率,有减少了代码体积 Scope Hoisting
optimization: { usedExports: true, // 开启tree shaking,不导出未使用的模块,打包代码还在 concatenateModules: true, // 压缩代码,去除未使用的代码 }
Tree Shaking & babel
使用babel-loader 会导致tree shaking 失效 Tree shaking前提是ES Module 由webpack打包的代码必须使用ESM 未处理esm新特性,可能会选择 babel-loader: ESM -> CommonJS,因此tree saking 会失效
babel插件集合@babel/preset-env会把esm的部分转换成commonjs,webpack再去打包,就会是以commonjs 的方式组织的代码打包
**在最新的版本的babel插件关闭了esm转换插件,转换之后还是esm的代码,tree shaking还是可以正常工作
sideEffects 副作用
一般用于npm包标记是否有副作用 production模式下自动开启
设置两个配置webpack.config.js、package.json webpack中配置
js
optimization: {
sideEffects: true // 开启功能
}
package.json 配置sideEffects: false标识当前项目中代码没有副作用 打包过后的代码就会移除未使用的代码 a.js b.js c.js
index.js 导出a.js,b.js,c.js
main.js 只导入使用a.js,
配置开启sideEffects副作用,就不会把b.js和c.js代码打包进去
使用sideEffects前提,确保你的代码真的没有副作用 extend.js
js
Number.prototype.add = function(size) {
let result = this + '';
while(result.length < size) {
result = '0' + result;
}
return result;
}
// 8.add(2) // 8 --> 008
在main.js 中
js
import './extend.js'; // 只是导入文件
8.add(2);
此时 extend.js并不会被打包到代码里 添加的add方法不能使用 我们项目css文件很多情况都是 import './index.css' 有时出现打包代码没有包含css文件,默认开启副作用,css代码没被打包进去 解决这个问题,在package.json中配置
js
sideEffects: [
"./src/extend.js",
"*.css"
]
打包结果就会包含这些代码
Code Splitting 分包/代码分割
webpack默认把所有代码都打包到一起bundle.js
所有模块打包到一起和分包是互斥的
场景 当模块越来越大时, 1.并不是每个模块在启动时都是必要的 2.分包,按需加载,打包成多个bundle
webpack配置分包 1.多入口打包 multi entry 多页应用程序 2.动态导入(esm的动态导入)
多入口打包
一个页面对应一个打包入口
js
entry: {
a: './src/a.js',
b: './src/b.js'
}, // 设置打包入库文件,如果是相对路径,./不能省略
output: {
filename: '[name].bundle.js',
},
plugins: [
new HtmlWebpackPlugin({
title: 'a页面',
template: './src/a.html', // 启用模板html
filename: 'a.html',
chunks: ['a'] // 配置chunks,否则打包文件都会被引入
}),
new HtmlWebpackPlugin({
title: 'b页面',
template: './src/b.html', // 启用模板html
filename: 'b.html',
chunks: ['b']
}),
]
公共的部分单独提取 不同的入口中肯定会有公共模块
js
optimization: {
splitChunks: {
chunks: 'all'
}
}, // 开启公共模块单独提取chunk,会生成一个单独的bundle
动态导入
动态导入的模块会被自动分包,
按需加载模块
动态导入无需配置webpack,执行按照esm中动态导入模块就可以,webpack会自动处理分包,按需加载
对于单页面应用开发,react/vue开发框架,路由映射组件就可以使用动态导入的方式实现动态加载
js
import('./common').then(mod => {
console.log('mod', mode);
})
按照动态导入,打包成的文件名称是序号,如果需要重命名 可以使用魔法注释
js
import(/* webpackChunkName: common */'./common').then(mod => {
console.log('mod', mode);
})
动态导入的模块打包的文件名就会说common 配置相同的chunkName会被打包到一起
MiniCssExtractPlugin (css文件超过150kb才考虑使用)
提取CSS文件插件,实现单独提取css模块到一个文件中 npm i mini-css-extract-plugin --dev
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
js
const MiniCssExtractPlugin = require('mini-css-rxtract-plugin');
....
module: {
rules: [
{
test: /.css$/,
use: [
MiniCssExtractPlugin.loader
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].bundle.css'
})
]
单独提出的css文件在生产环境下打包,没有被压缩,因为webpack内部的压缩是针对js文件压缩
OptimizeCssAssetsWebpackPlugin 压缩css
npm i optimize-css-assets-webpack-plugin --dev
js
const OptimizeCssAssetsWebpackPlugin = require('mini-css-rxtract-plugin');
// 配置在optimization中minimizer数组中,这样配置使得内置的js压缩插件失效
// 因此要配置TerserWebpackPlugin
const TerserWebpackPlugin = require('terser-webpack-plugin');
...
optimization: {
minimizer: [
new OptimizeCssAssetsWebpackPlugin(),
new TerserWebpackPlugin()
]
}
输出文件名Hash
substitutions
如果客户端文件缓存时间设置很长,文件名相同,就会一直是缓存, 文件名称改变,就可以请求新的文件
生产模式下,文件名使用hash
[hash]
plugins: [ new MiniCssExtractPlugin({ filename: '[name]-[hash].bundle.css' }) // x项目基本的hash,即项目中任何文件改变,生成的文件名(hash)都会变化 ]
[chunkhash] // chunk hash,哪个文件变化,打包的哪个文件名变化(hash)
[contenthash] // 文件级别的hash,不同的文件不同的hash值 ---最适合解决文件缓存问题
指定hash长度 [contenthash:8] 指定8位长度的hash