文章说明:本文章为拉钩大前端训练营所做笔记和心得,若有不当之处,还望各位指出与教导,谢谢 !
模块打包工具
一、模块打包工具的由来
随着引用模块化,我们的应用会产生新的问题:
1.ES Modules 存在环境兼容问题
2.模块文件过多,网络请求频繁(每一个我们需要请求的文件都要从网络请求当中请求回来,影响工作效率)
3.所有的前端资源都需要模块化
毋容置疑,模块化是必要的
将所有es 6和一些新特性的代码转化为浏览器均能兼容运行的代码,以及能将所有散落的文件全部打包到一个文件里,支持不同类型的资源模块
二、模块打包工具概要
webpack:
模块打包器(Module bundler):将零散的模块代码打包到同一个js文件当中。
模块加载器(Loader):对于代码中有环境兼容问题的代码,对其进行编译转换。
代码拆分(Code Spilitting):将应用当中所有的代码按照我们的需要打包,这样我们就不用担心把所有代码全部打包到一起产生文件比较大的问题,可以把应用加载过程当中初次运行所必须的模块打包到一起,对于其它的模块再单独存放,等到应用过程中实际需要某个模块,我们再去加载这个模块,从而实现增量加载或者渐进加载,不用担心文件或者文件太大这两个极端问题。
资源模块(Asset Module):以模块化的方式加载任意资源文件
打包工具解决的是前端整体的模块化,并不单指JavaScript模块化
wabpack
一、快速上手
1.一个src文件夹下新建两个文件:heading.js和index.js,index.js将heading.js模块引入并使用了里面的方法,根目录创建了html文件。
heading.js:
export default () => {
//默认导出的一个用于创建元素的函数
const element = document.createElement('h2')
element.textContent = 'Hello world'
element.addEventListener('click', () => {
alert('Hello webpack')
})
return element
}
index.js:
import createHeading from './heading.js'
const heading = createHeading()
document.body.append(heading)
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Webpack - 快速上手</title>
</head>
<body>
<!-- 引入模块 -->
<script type="module" src="src/index.js"></script>
</body>
</html>
2.webpack是一个NPM工具模块,需要初始化包管理文件:package.json
yarn init --yes //或者 npm init -y
3.安装webpack核心模块,以及对应的webpack-cli模块
$ yarn add webpack webpack-cli --dev // 或者 npm i webpack webpack-cli --save-dev
4.查看webpack是否安装成功
$ yarn webpack --version
5.使用 webpack ,进行打包
yarn webpack
可以看到,打包后,会生成一个 dist 文件夹,里面包含一个 main.js
6.在package.json中,使用 NPM Scripts 对打包命令进行包装,
package.json:
{
"scripts": {
"build": "webpack"
}
}
7.将 index.html 中引入的 js,进行修改
<script src="dist/main.js"></script>
二、配置文件
webpack 4.0+ 支持按照约定的内容,进行打包,即 src/index.js(默认入口文件) 打包到 dist/main.js。
在项目根目录添加webpack.config.js配置文件:
const path = require('path')
module.exports = {
//指定webpack打包入口文件的路径
entry: './src/main.js',
//输出文件的位置
output: {
//指定输出文件的名称
filename: 'bundle.js',
//指定输出文件所在的目录,必须为绝对路径
path: path.join(__dirname, 'output')
}
}
三、webpack工作模式
理解为针对不同环境预设的配置,使用yarn webpack 打包时,控制台会打印一个配置警告,大致意识是没有设置一个叫做mode的属性,webpack会默认使用production模式去工作,在这个模式下面webpack会默认去启动一些优化插件,例如把我们的代码进行压缩。通过cli参数指定打包的模式,具体的用法就是给webpack命令传入一个 --mode的参数,这种属性有三种取值,默认是production。
- development(开发模式),优化打包的速度
$ yarn webpack --mode development
- production(生产模式),启动多个插件,进行代码的压缩等
$ yarn webpack --mode production
- none,运行最原始状态的打包,不会做任何的处理
$ yarn webpack --mode none
也可以在webpack.config.js里面配置,添加mode属性,然后再指定。
const path = require('path')
module.exports = {
// 这个属性有三种取值,分别是 production、development 和 none。
// 1. 生产模式下,Webpack 会自动优化打包结果;
// 2. 开发模式下,Webpack 会自动优化打包速度,添加一些调试过程中的辅助;
// 3. None 模式下,Webpack 就是运行最原始的打包,不做任何额外处理;
mode: 'development',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
}
}
四、Webpack 资源模块加载
通过webpack去引入前端项目中的任意类型文件,例如加载css资源文件
安装css-loader模块和style-loader模块:
$ yarn add css-loader --dev # or npm i css-loader --save-dev
$ yarn add style-loader --dev # or npm i style-loader --save-dev
在css文件中书写body的样式:
main.css:
body {
margin: 0 auto;
padding: 0 20px;
max-width: 800px;
background-color: #000000;
}
在 webpack.config.js 中进行配置:
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.css',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
},
module: {
//对其它模块资源加载规则的配置,每个规则对象都需要设置两个属性
rules: [
{
//正则表达式,用来匹配我们打包过程当中文件的路径
test: /.css$/,
//指定匹配到文件需要使用的loader,如果有多个loader执行时从后往前
use: [
//把cssloader转换过后的结果通过style标签的形式追加到页面上
'style-loader',
//将css文件转化为bundle.js里面的一个模块
'css-loader'
]
}
]
}
}
五、Webpack导入资源模块
打包入口从某种程度上来说是运行入口,JavaScript驱动这个前端应用的业务,正确做法还是把js文件作为打包的入口,通过在js代码当中import的方式引入css文件,cssloader仍然可以正常工作
在main.js中引入css文件:
import createHeading from './heading.js'
import './main.css'
const heading = createHeading()
document.body.append(heading)
在heading.js当中引入heading.css文件
import './heading.css'
export default () => {
const element = document.createElement('h2')
element.textContent = 'Hello world'
element.classList.add('heading')
element.addEventListener('click', () => {
alert('Hello webpack')
})
return element
}
webpack建议我们在编写代码过程中去引入任何当前这个代码所需要的的资源文件,需要资源的不是应用,而是代码。是你的代码想要正常工作就必须要去加载对应的资源。
优势:逻辑合理,JS确实需要这些资源文件。确保上线资源不缺失,都是必要的。
六、webpack文件资源加载器
还有一些我们经常用到的资源文件例如项目当中的图片或者字体,对于这类的文件,我们需要用到文件资源加载器也就是fileloader。
安装loader 模块:
$ yarn add file-loader --dev # or npm i file-loader --save-dev
在mian.js引入图片
import createHeading from './heading.js'
import './main.css'
//导入图片后要接受下资源模块的默认导出,因为这个导出成员就是打包过后的资源路径
import icon from './icon.png'
const heading = createHeading()
document.body.append(heading)
//有了这个路径过后创建一个图片对象,也就是image元素
const img = new Image()
//把资源的路径设置为src
img.src = icon
//最后把这个元素append到body当中
document.body.append(img)
在 webpack.config.js 中添加配置规则:
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
//因为生成的文件放在dist目录下,设置网站的根目录,/符号不能省略,默认为 ''
publicPath: 'dist/'
},
module: {
rules: [
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
//配置图片规则
test: /.png$/,
use: 'file-loader'
}
]
}
}
webpack在打包时遇到图片文件然后根据配置文件的配置匹配到对应的文件加载器,此时文件加载器就开始工作了,先是将文件拷贝到输出目录,然后将文件拷贝到输出目录的路径作为当前模块的返回值返回,对于应用来说所需要的资源就被发布出来了,同时根据模块的导出成员拿到这个资源的访问路径。
七、Webpack URL加载器
通过Data URLs去表示文件的方式也非常常见,Data url是一种特殊的url协议,可以用来直接去表示一个文件,传统的url一般要求服务器上有一个对应的文件,然后通过请求这个地址得到服务器上对应的文件,dataurl就是当前可以表示文件内容的方式,这种文本就已经包含了文件内容,使用这个时不会再去发送任何请求:
如果是图片或者字体这一类无法通过文本去表示的二进制类型的文件,可以通过将文件的内容进行base64编码,以base64编码过后的结果也就是一个字符串去表示这个文件的内容:
如下:
在webpack打包静态资源文件时同样可以使用这种方式去实现,通过dataurl可以以代码的形式去表示任何类型的文件,需要用到一个专门的加载器:url-loader(适合转换体积比较小的文件资源)
安装url-loader:
yarn add url-loader --dev
在webpack.config.js中进行配置
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
publicPath: 'dist/'
},
module: {
rules: [
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.png$/,
use: {
//将其转换为dataurl的形式
loader: 'url-loader',
options: {
// 设置配置选项
limit: 10 * 1024 // 10 KB 单位字节,只匹配 10 KB 以下的
}
}
}
]
}
}
最佳实践:
1.小文件使用 Data URLs,转换为 Data URLs 嵌入代码中,减少请求次数;
2.大文件单独提取存放,提高加载速度
注意
在对 url-loader 适合的文件大小进行限制后,需要同时安装 file-loader,因为超出限制的文件,会去查找 file-loader 进行转换。如果不安装 file-loader,将会因为找不到,而引起程序报错.
八.Webpack 常用加载器分类
加载器用来处理和加工我们打包过程当中所遇到的资源文件,分为三类:
1.编译转换类:这种类型的loader会把我们加载到的资源模块转换为JavaScript的代码,例如之前所用到的cssloader:
2.文件操作类:都会把我们加载到的资源模块拷贝到输出的目录同时将这个文件的访问路径向外导出,例如file-loader:
**3.代码检查类:**对于加载到的资源文件一般是代码去进行校验的加载器,这种加载器目的是为了统一代码的风格,从而提高质量,不会去修改我们生产环境的代码:
九、Webpack 与 ES 2015
Webpack仅仅是对模块完成打包工作,因为模块打包需要,所以要处理import和export,除此之外并不能转换代码当中其它的es6特性,需要安装babel-loader 插件转换,用来将 ES6+ 的新特性,转换为 ES5,需要 @babel/core 核心模块,以及用于去完成具体特性转换插件的集合 @babel/preset-env:
$ yarn add babel-loader @babel/core @babel/preset-env --dev
在 webpack.config.js 中添加配置规则:
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
publicPath: 'dist/'
},
module: {
rules: [
{
// 通过 babel-loader 取代默认的加载器,处理代码中的新特性
test: /.js$/,
use: {
//打包过程中能处理代码中的新特性
loader: 'babel-loader',
options: {
//这个插件集合包含了全部的es的最新特性
presets: ['@babel/preset-env']
}
}
},
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.png$/,
use: {
loader: 'url-loader',
options: {
limit: 10 * 1024 // 10 KB
}
}
}
]
}
}
总结:
1.Webpack 只是 打包工具;
2.加载器可以用来编译转换代码
十、Webpack 模块加载方式
1.遵循ES Modules 标准的import声明
2.遵循CommonJS标注的require函数
3.遵循AMD标准的define函数和require函数
webpack兼容多种模块化标准,除非必要的情况否则一定不要在项目当中混合使用以上这些标准。还有一些独立的加载器在工作也会去处理我们加载到的资源当中导入的模块,例如cssloader加载的文件,4.里面的@import属性和url函数也会触发资源模块加载,5.HTML代码中图片标签的src属性也会去触发相应的模块加载
html-loader: 来处理 HTML 中制定的标签属性。
安装loader模块:
$ yarn add html-loader --dev
在 HTML 中编写代码,footer.html:
<footer>
<img src="better.png" alt="better" width="256">
<a href="better.png">download png</a>
</footer>
在 JS 文件中进行导入,main.js:
import footerHtml from './footer.html'
//接收导出的html字符串,将其输出到页面当中
document.write(footerHtml)
在 webpack.config.js 中,添加配置:
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
publicPath: 'dist/'
},
module: {
rules: [
{
test: /.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.png$/,
use: {
loader: 'url-loader',
options: {
limit: 10 * 1024 // 10 KB
}
}
},
{
test: /.html$/,
use: {
loader: 'html-loader',
options: {
//html加载的时候对页面上一些额外的属性进行处理,这个属性默认只有'img:src'
attrs: ['img:src', 'a:href']//支持img的src和a的href属性
}
}
}
]
}
}
十一、Webpack核心工作原理
在项目当中散落着各种各样的资源文件,webpack会根据配置,找到其中的文件作为打包的入口(js),然后会去顺着入口文件的代码根据代码中出现的import,require之类的语句解析推断出来这个文件所依赖的资源模块,然后分别去解析每一资源模块对应的依赖,
最后形成了整个项目文件之间的一个依赖关系的依赖树,webpack会递归这个依赖树然后找到乜咯节点对应的资源文件,最后根据配置文件当中的rules属性去找到这个模块对应的加载路径,然后交给对应的加载器去加载这个模块,最后将加载到的结果放入到bundle.js也就是打包结果当中,从而实现整个项目的打包,loader的机制是webpack的核心:
十二、Webpack Loader的工作原理
webpack加载资源的过程有点类似于工作管道,可以在这个过程中依次使用多个loader,但是它要求我们最终管道工作过后的结果必须是一段JavaScript代码。
1.新建一个名为markdown-loader的项目,建一个src文件夹,文件目录如下,md文件内容随意;
2.mian.js入口文件,并且引入xxx.md文件:
import about from './about.md'
console.log(about);
3.根目录下的html文件为页面展示的入口文件
4.初始化包管理文件package.json
$ yarn init --yes # or npm init -y
5.安装 webpack 模块,以及其依赖命令模块 webpack-cli
$ yarn add webpack webpack-cli --dev # or npm i webpack webpack-cli --save-dev
6.在根目录下,新建 markdown-loader.js,即 md 文件的转换 loader
7.在根目录下,新建 webpack.config.js,并设置对 md文件的规则配置:
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
publicPath: 'dist/'
},
module: {
rules: [
{
// 用来转换的 loader 既可以是一个模块,也可以是一个 js 文件
test: /.md$/,
use: './markdown-loader'
}
]
}
}
8.markdown-loader.js 文件需求,输入:就是每次加载到的资源文件;输出:就是此次加工后的结果,在 markdown-loader.js 中进行配置:
// 通过 source 参数,接收输入
module.exports = source => {
console.log(source);
return `console.log('hello ~')`
}
可以看到,使用的 js 的代码标准进行了输出字符串,这是因为 loader 要求输出结果必须是一段标准的 JavaScript代码。
9.若要解析 md 文件,则需要安装 md 文件的解析模块:
$ yarn add marked --dev # or npm i marked --save-dev
10.在 markdown-loader.js 中,使用 marked 模块解析 md 文件,并将结果以模块形式导出,markdown-laoder.js:
const marked = require('marked') // 导入 md 文件的解析模块
module.exports = source => {
// 解析 source, 返回值是一串html字符串,即转换后的结果
const html = marked(source)
// 返回 html字符串,交给下一个 loader 处理
return html
}
11.安装用于处理 html 加载的 loader
$ yarn add html-loader --dev # or npm i html-loader --save-dev
12.修改 webpack.config.js 配置文件:
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
publicPath: 'dist/'
},
module: {
rules: [
{
test: /.md$/,
use: [
'html-loader',
'./markdown-loader' // 按照 从后往前 的顺序进行执行
]
}
]
}
}
十三、Webpack 常用插件
插件机制是webpack当中另外一个核心特性,目的是为了增强Webpack自动化方面的能力。loader专注实现资源模块加载,plugin解决其他自动化工作,plugin可以实现打包之前自动清除dist目录,拷贝静态文件到输出目录,压缩打包结果输出代码,实现大多前端工程化的工作。
1.clean-webpack-plugin
1)自动清除输出目录插件,安装插件模块:
$ yarn add clean-webpack-plugin --dev
2).在webpack.config.js中添加插件配置
// 导入 clean-webpack-plugin 插件,这个插件导出一个 CleanWebpackPlugin 成员
const {
CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
// ...
plugins: [ // plugins 专门用来配置插件的属性
// 一般插件导出的都是一个类型,通过这个类型创建实例
// 然后将这个实例,放入到 plugins 数组中
new CleanWebpackPlugin()
]
}
plugins 属性,是一个数组,它与 module 属于同级。
2.html-webpack-plugin
自动生成 HTML 插件,不需要在手动书写 html 入口文件
安装模块:
$ yarn add html-webpack-plugin --dev
在 webpack.config.js 中,导入插件,并进行配置
const path = require('path')
const {
CleanWebpackPlugin } = require('clean-webpack-plugin')
// html-webpack-plugin 默认导出的就是一个类型,无需解构其内部成员
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
// publicPath: 'dist/' // 自动生成 html 时,不需配置
},
plugins: [
new CleanWebpackPlugin(),
// 添加 HtmlWebpackPlugin 实例对象
// 自定义输出文件内容:给构造函数传入对象参数,指定配置选项
new HtmlWebpackPlugin({
// 用于生成 index.html
title: 'Webpack Plugin Sample', // 设置 HTML 的标题
meta: {
// 设置页面中的 元数据标签
viewport: 'width=device-width'
},
// 指定模板文件,使生成的HTML文件根据模板文件进行生成
template: './src/index.html'
}),
// 同时输出多个页面文件, 即可以添加多个 HtmlWebpackPlugin 实例
// 每一个 HtmlWebpackPlugin 实例,就是用来生成一个 HTML文件的
new HtmlWebpackPlugin({
// 用于生成 about.html
filename: 'about.html' // 指定输出的文件名
})
]
}
3.copy-webpack-plugin
将不需要编译转换的 静态资源文件 拷贝到输出目录,一般存放在 public 目录下:
安装插件模块
$ yarn add copy-webpack-plugin --dev
在 webpack.config.js 中,导入插件,并进行配置:
const path = require('path')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
// ...
plugins: [
// ...
// 传入 数组参数,用于指定需要去拷贝的文件路径
new CopyWebpackPlugin([
// 'public/**' // 可以是一个目录,通配符,文件路径
'public' // 表示会将 public 目录下所有文件全部拷贝到输出目录
])
]
}
十四、Webpack 插件原理与开发一个插件
相比于Loader,plugin拥有更宽的能力范围,在软件开发过程中,plugin通过钩子机制实现,类似于web当中的事件,在webpack工作过程当中有很多的环节,为了便于插件的扩展,webpack几乎给每一个环节都埋下了钩子,这样开发插件的时候就可以通过往这些不同的节点上挂载不同的任务就可以轻松的扩展webpack的能力
webpack要求插件必须是一个函数或者是一个包含apply方法的对象,一般把这个插件对应为一个类型,然后这个类型当中去定于一个apply方法,使用的时候通过这个类型去构建一个实例然后去使用。
开发一个plugin插件:功能是删除bundle.js中生成的注释
1.在 webpack.config.js 中,进行插件的创建和使用
class MyPlugin {
// 定义一个插件类型
/**
* 在 webpack 启动时,自动被调用
* @param {*} compiler
* compiler 对象参数,是 webpack 工作过程中最核心的一个对象
* compiler 对象参数,包含了此次构建的所有配置信息
* 通过 compiler 对象注册钩子函数
*/
apply(compiler) {
console.log('MyPlugin 启动');
/**
* 通过 compiler.hooks 可以访问到钩子
* 通过 tap 方法去注册钩子函数
* tap方法,接收两个参数:
* -- 第一个参数:插件的名称
* -- 第二个参数:需要挂载到钩子上的函数
*/
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation 对象 => 可以理解为此次打包的上下文
// 所有打包的结果,都会放到 compilation 对象当中
for (const name in compilation) {
// 对象中的键(属性名),代表每一个资源文件的名称
// 判断只对 js文件 进行处理
if (name.endsWith('.js')) {
// assets 获取即将写入目录当中的资源文件信息
// source() 拿到对应的资源文件的内容
const contents = compilation.assets[name].source()
// 使用正则替换代码中的注释
const withoutComments = contents.replace(/\/*\**\*\//g, '')
// 将结果 覆盖到原有的文件中
compilation.assets[name] = {
source: () => withoutComments, // 返回新内容
size: () => withoutComments.length // 返回内容的大小,必须方法
}
}
}
})
}
}
module.exports = {
// ...
plugins: [
// ...
// 应用 MyPlugin 插件
new MyPlugin()
]
}
十五、Webpack 体验优化
自动编译:监听文件变化,自动重新打包
直接在命令行启动 watch 工作模式,监听文件变化,自动重新打包:
$ yarn webpack --watch
在 package.json 中,配置 NPM Scripts
{
"scripts": {
"build": "webpack --watch"
}
}
可以看到,开启 watch 工作模式以后,打包命令会一直处于工作状态,当文件修改后,会自动进行打包,直到手动结束 cli 命令。
自动刷新
使用BrowserSync启动热更新开发 Web服务器,实现自动刷新浏览器
全局安装 BrowserSync 模块:
$ yarn global add browser-sync # or npm i browser-sync -g
使用命令启动 web服务器:
$ browser-sync dist --files "**/*"
缺点:
1.操作太麻烦2.效率上降低了。
Dev Server:
Webpack Dev Server,是 Webpack 官方推出的开发工具,提供用于开发的 HTTP Server,集成了 自动编译 和 自动刷新浏览器 等功能。
版本说明:
本次安装 Webpack Dev Server 版本,要求 webpack 4 版本以下。webpack 5+ 版本已经集成了 Webpack Dev Server 开发工具,无需再进行安装,直接使用 webpack server 即可开启服务。
注意:Webpack Dev Server 的版本,要比 Webpack 的版本低一级,否则无法使用。
安装 开发工具:
$ yarn add webpack-dev-server --dev
运行命令,使用 --open 自动唤醒浏览器,并打开运行地址
$ yarn webpack-dev-server --open
执行说明:
1)运行时,内部自动使用 webpack 进行打包;
2)启动 HTTP Server,自动运行打包结果;
3)自动监听代码变化,自动立即重新打包;
4)打包结果不会写入到磁盘中,打包结果会暂时存放在内存中;
5)HTTP Server 会从内存中读取这些文件,然后发送给浏览器。
优势:
Webpack Dev Server,会减少很多磁盘读写操作,从而大大提高构建效率。
问题:
Dev Server 默认只会 serve 打包输出文件,即只要是 webpack 打包输出的文件,都可以直接被访问。其他静态资源如果也需要 serve,则需要去告知 webpack。
Dev Server 静态资源访问:
1.配置静态资源访问,webpack.config.js:
const path = require('path')
const {
CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
// 专门为 webpack-dev-server 指定的配置选项
devServer: {
// 额外为开发服务器指定查找静态资源目录,可以是字符串或数组,配置一个或多个
contentBase: ['./public']
},
plugins: [
new CleanWebpackPlugin(),
// 用于生成 index.html
new HtmlWebpackPlugin({
title: 'Webpack Tutorials',
meta: {
viewport: 'width=device-width'
},
template: './src/index.html'
}),
// 开发阶段最好不要使用这个插件
// 一般放在上线前的最后一次打包使用
// 开发阶段需要频繁的serve, 多次使用插件会影响运行效率
// new CopyWebpackPlugin(['public'])
]
}
2.开发阶段接口跨域问题,需要配置 代理API,webpack.config.js:
module.exports = {
// 专门为 webpack-dev-server 指定的配置选项, 开发阶段的配置
devServer: {
// 额外为开发服务器指定查找静态资源目录,可以是字符串或数组,配置一个或多个
contentBase: ['./public'],
// proxy属性,用来添加代理服务配置
proxy: {
/**
* 每一个属性就是一个代理规则的配置
* 属性名: 需要被代理的请求路径前缀,
* 即请求以哪一个地址开始,就会走对应的代理请求
* 属性值: 为这个前缀所匹配到的代理规则配置
*/
'/api': {
// http://localhost:8080/api/users => https://api.github.com/api/users
target: 'https://api.github.com', // 代理目标
// http://localhost:8080/api/users => https://api.github.com/users
// 如果代理目标地址中没有‘/api’,则需要重写代理目标地址
pathRewrite: {
'^/api': '' // 以正则的形式进行匹配,以 ^ 开头
},
// 不能使用 localhost:8080 作为请求 GitHub 的主机名
// 设置改变主机名
changeOrigin: true
}
}
}
}
十六、Source Map
Source Map是去映射我们转换后的代码与源代码之间的关系,一段转换过后的代码我们通过转换过程当中生成的sourcemap文件就可以逆向得到源代码。目前很多第三方库在去发布的文件当中都会有一个.map后缀的sourcemap文件。主要用来在开发阶段进行调试和定位错误。
基本使用
1.引用 Source Map 的注释
//# sourceMapppingURL=jquery-3.4.3.min.map
在浏览器中,打开开发人员工具,开发人员工具加载到的 js文件最后存在上面的注释,会自动去请求 Source Map 文件,然后根据文件的内容逆向解析出对应的源代码,以便于调试。又因为存在映射关系,所以源代码中如果出现错误,就会很容易定位到源代码中错误的位置。
2.在webpack.config.js中,配置Source Map:
// 其余代码省略
module.exports = {
// 配置开发过程中的辅助工具,
// 也就是与 Source Map 相关的一些功能配置
devtool: 'source-map',
}
3.运行打包命令,查看bundle.js底部是否存在Source Map的注释
yarn webpack
查看 bundle.js 底部,如下图所示:
Webpack 目前支持 12 种不同的方式,每种方式所生成的 Source Map 效果,以及生成 Source Map 的速度都是不一样的。效果最好的,生成速度越慢;反之,生成最快的,生成的 Source Map 效果不好,几乎没有。
12中方式对比,如下图所示:
webpack中eval模式下的Source Map
eval模式:eval是js当中的函数,可以用来运行字符串当中的字符串代码。默认情况下,运行的代码会运行在一个临时的虚拟机环境中,可以通过 sourceURL 来声明这段代码所属的文件路径。
基本使用:
1.在浏览器控制台,进行测试
eval('console.log(123)//# sourceURL=./foo/bar.js')
点击./foo/bar.js,显示效果
2.在 webpack.config.js 中,进行配置
module.exports = {
// 配置开发过程中的辅助工具,
// 也就是与 Source Map 相关的一些功能配置
devtool: 'eval',
}
l浏览器定位错误,如下图所示:
可以看到,当点击进去的时候,显示的却是打包过后的模块代码。在 eval 模式下,会将每个模块的js 代码都放到 eval() 函数中执行,在执行的字符串最后通过 sourceURL 的方式去说明所对应的文件路径,这样浏览器通过 eval 在执行代码时,就会知道代码所对应的源代码是哪一个文件,从而去定位错误所出现的文件。eval 模式下,不会生成 Source Map 文件,因此构建速度最快,但是其效果比较简单,只能知道源代码文件的名称,而不知道具体的行列信息。
模式对比
一次打包过程中,同时生成所有模式下的不同结果。
基本使用:
1.在webpack.config.js中,进行配置
const HtmlWebpackPlugin = require('html-webpack-plugin')
const allModes = [
'eval',
'cheap-eval-source-map',
'cheap-module-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',
'hidden-source-map',
'nosources-source-map'
]
module.exports = allModes.map(item => {
return {
devtool: item,
mode: 'none',
entry: './src/main.js',
output: {
filename: `js/${
item}.js`
},
module: {
rules: [
{
test: /\.js$/,
use: {
// 辨别其中一类模式的差异
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
plugins: [
// 为每一个打包任务生成一个 HTML文件
new HtmlWebpackPlugin({
filename: `${
item}.html`
})
]
}
})
2.使用 http-server 开启服务器
http-server dist
3.通过查看每一个HTML文件,得出各个模式的对比结果。
带有 module 的模式,不会经过 loader 的编译转换,生成的 Source Map 就是项目的源代码。而不带有 module 的模式,则是经过 babel 转换后的代码。hidden-source-map,一般用在生成第三方包的时候,需要生成 Source Map 文件,但又不想在 包 中引入。nosources-source-map,用在生产环境中,保护源代码不被暴露。
选择合适的 Source Map
下面是建议选择的模式,根据具体情况具体选择。
开发模式,选择cheap-module-eval-source-map
1.代码每行不会超过 80 个字符;
2.代码经过 Loader 转换过后的差异较大;
3.首次打包速度慢无所谓,重写打包相对较快。
生产模式,选择 none
1.Source Map 会暴露源代码;
2.调试是开发阶段的事情。
生产模式,方便定位错误,选择 nosource-source-map
1.不会向外暴露源代码;
2.出现错误时,可以找到源代码对应的位置。