开发工具诞生的目的永远是加速开发. 程序员应该不断追求更快更好的开发工具.
前文一步步学Webpack4(0)-- 实战起步已经完成了Webpack环境的搭建以及实现了一句命令自动打包项目,这一次我们继续使用之前的项目webpack-stepbystep来尝试搭建适合对开发者友好的项目开发环境.
本章按照以下步骤进行:
- 开发需求总结:跟随官方文档 Development ,总结前端开发者对于调试与开发的需求;
- 练习1:Webpack开发工具认识与选择;
- 练习2:Webpack热模块更新(HMR);
- 练习3:认识loader并完成基础配置;
写Webpack文章不写版本都是耍流氓,这篇文章基于当下最新的 webpack v4.22.0 以及 webpack-cli v3.1.2 编写.
1. 开发需求探索
Eating your own dog food
尝试深入探索学习Webpack的人大概都有一颗想给自己写个顺手的手脚架的心吧,吃自己的狗粮这件事对开发者肯定是好事,但是前提是自己真正懂得自己的需求.
对于一个普通前端开发者来说,一个简单项目的手脚架必须具备一定的能力,总结一下一些必不可少的需求吧:
- 方便的调试信息追溯;
- 代码修改之后自动打包;
- 代码修改之后自动更新页面内容;
接下来我们就来借助Webpack的能力,一个个实现这些需求~
2. 练习1:Webpack开发工具认识与选择
2.1 source map
实现调试信息追溯
文章跟随 一步步学Webpack4(0)-- 实战起步 继续开发.
项目已经能够使用Webpack打包了,我们现在使用这个项目来随便写点会发生错误的代码,例如在方法第一行加入 console.abg('generate component')
:
index.js
import _ from 'lodash';
function component () {
console.abg('generate component');
let element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
return element;
}
document.body.appendChild(component());
然后在终端中运行 webpack
完成打包,运行结果如下:
发现错误是被指向了编译后的文件 main.js
,这并不是我们想要的. 发生错误的时候浏览器如果不能追溯到源代码发生错误的位置,这将增大调试的难度,幸好Webpack已经提供解决这个问题的方法--source map
,我们只需要简单地修改一下配置文件:
webpack.config.js
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
devtool: 'inline-source-map',
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
inject: false,
template: 'index.html',
filename: 'index.html'
})
]
};
上面代码中加入了一句 devtool: 'inline-source-map'
, 重新在终端中运行 webpack
完成打包,运行结果如下:
成功了,借助source map
的力量, 错误发生时浏览器从 main.js
追溯到了源代码 index.js
中. 至此我们已经成功实现了第一个需求“方便的错误信息追溯”. 另外要特别注意的是,source map
只能在开发环境中使用以方便调试,千万不能用于生产环境,简单原因看看添加了 source map
之后的main.js文件大小就知道了(逃
当然 source map
还有许多配置可以选择, 不过与本章的学习关系不大, 先继续往下学习吧~
2.2 试用开发工具
刀耕火种时期每次保存完代码都要F5,在项目中应用了Webpack之后每次保存完代码居然需要先Webpack打包再F5,这么愚蠢的事情程序员怎么可能允许呢,于是开发工具们开始诞生了:
2.2.1 Webpack观察者模式(webpack's Watch Mode)
严格来说这不算是一种额外的开发工具,这只是Webpack的一种运行模式,可以在终端输入 webpack --watch
开始持续监听文件变化,只要修改代码并保存,webpack将会自动帮你打包项目,听起来还不错能够自动打包,但是这种模式并不能帮助开发者更新页面内容也就是说, 你还是需要自己按F5刷新..., 感觉还是有点惨啊.
算了 =。= Next one
2.2.2 webpack-dev-server(推荐)
这是一个官方推荐的新手友好的开发工具,webpack-dev-server
提供了一个具备实时重载功能的简单服务器,只需要下载到工程中,并对配置文件进行简单配置即可使用,安装命令如下:
npm i -D webpack-dev-server
此处基本使用 webpack-dev-server 的默认配置,唯一修改的地方是配置开启服务器的位置即 contentBase: './dist'
,整份配置如下所示:
webpack.config.js
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist'
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
inject: false,
template: 'index.html',
filename: 'index.html'
})
]
};
接下来打开 package.json
文件,在"scripts"中添加一行运行脚本 "start": "webpakc-dev-server --open"
,完整package.json文件如下:
package.json
{
"name": "webpack-stepbystep",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"start": "webpack-dev-server --open"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"clean-webpack-plugin": "^0.1.19",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.22.0",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
},
"dependencies": {
"lodash": "^4.17.11"
}
}
然后打开终端,运行命令 npm start
,稍后就能看到浏览器自动打开了 localhost:8080 界面,如果对代码进行保存修改,web服务器就会自动重新打包代码并将更新应用到浏览器网页上~至此前端程序员的对于手脚架的几个需求已经完全得到了满足,现在已经可以舒舒服服地开始前端开发了~
至此项目提交为 feat(project): add source map & devtools .
P.S. 官方文档中还有一个开发工具 webpack-dev-middleware
, 与Node.js结合能够进行更多的自定义配置,不过暂时我们不需要用到它.
3. 练习2:Webpack热模块更新(HMR)
3.1 修改错误代码测试
完成上一小节的配置之后,我们可以开始尝试在当前项目中编写代码了,首先我们当然是先来改正第一小节的错误,将 index.js 中的console.abg('generate component');
改为 console.log('generate component');
,文件完整代码如下所示:
index.js
import _ from 'lodash';
function component () {
console.log('generate component');
let element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
return element;
}
document.body.appendChild(component());
保存之后,很快就能看到页面上出现了熟悉的 Hello Webpack~ 修改代码之后只需要保存,剩下的事情Webpack都会帮你自动搞定,自动更新的效果不错嘛~
然而,这只是一个小小的项目。设想一下,你现在正在调试一个规模比较大的项目,在最后一步按下"提交button"之前,你突然想起"提交button"绑定错了触发的事件. 如果此时修改代码并保存,应用页面将会被刷新,也就是说你刚刚选择的许多选项的状态会被重置回初始值。你的粗心让你需要把之前的选择流程走一遍,如果之后又发现了另一个小错误那么又要再走一遍流程...此时的你多么希望有一个工具能让你保持着页面当前的状态,并偷偷地帮你更新修改好的绑定关系, 你只需要在完成更新后从容按下"提交button"就完事. 没错,这就是这一小节的重点 模块热更新 Hot Module Replacement(HMR)!
3.2 实战:简单模块热更新(HMR)
说了那么多,不如show me your code. 好,现在马上通过实战来见识一下 HMR
的厉害.
3.2.1 实战准备
我们新建一个模块称为printMe, 负责打印一段文字, 在index.js中引用该模块并为编写一个button来触发它,完整代码如下所示:
print.js
export default function printMe () {
console.log('Updating print.js');
}
index.js
import _ from 'lodash';
import printMe from './print';
function component () {
let element = document.createElement('div');
let btn = document.createElement('button');
element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
btn.innerHTML = 'Click me and check the console.';
btn.onclick = printMe;
element.appendChild(btn);
return element;
}
document.body.appendChild(component());
3.2.2 实时刷新测试
修改 print.js
的打印内容并保存,当前效果是:整个页面直接通过刷新来更新界面.
3.2.3 应用模块热更新
-
步骤一:首先更新webpack的配置文件. 在配置头部加入对webpack的引用,然后在devServer对象中配置
hot: true
来开启HMR
,最后在plugins对象中配置HotModuleReplacementPlugin
插件以替换模块,完整代码如下所示:webpack.config.js
const path = require('path'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist') }, devtool: 'inline-source-map', devServer: { contentBase: './dist', hot: true }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ inject: false, template: 'index.html', filename: 'index.html' }), new webpack.HotModuleReplacementPlugin() ] };
-
步骤二:修改 index.js 文件,在底部加入
HMR
相关代码,令其在 printMe 模块发生改变时可以接受更新的模块,完整代码如下所示:index.js
import _ from 'lodash'; import printMe from './print'; function component () { let element = document.createElement('div'); let btn = document.createElement('button'); element.innerHTML = _.join(['Hello', 'Webpack'], ' '); btn.innerHTML = 'Click me and check the console.'; btn.onclick = printMe; element.appendChild(btn); return element; } document.body.appendChild(component()); if (module.hot) { module.hot.accept('./print.js', function () { console.log('Accepting the updated printMe module!'); printMe(); }); }
- 步骤三:修改 print.js 的打印文字并保存,通过观察控制打印结果,发现页面完成了修改并且没有产生刷新.
-
步骤四:你以为就这样结束了?其实并没有,HMR 手撸的话还是比较坑的. 点击button你会发现控制台中打印的东西一直都是最初始的打印值,这是因为button的事件依然绑定在旧的函数上,为了解决这个问题,我们将通过 index.js 底部
HMR
代码更新button的事件绑定,具体完整代码如下所示:index.js
import _ from 'lodash'; import printMe from './print'; function component () { let element = document.createElement('div'); let btn = document.createElement('button'); element.innerHTML = _.join(['Hello', 'Webpack'], ' '); btn.innerHTML = 'Click me and check the console.'; btn.onclick = printMe; element.appendChild(btn); return element; } // document.body.appendChild(component()); let ele = component(); document.body.appendChild(ele); if (module.hot) { module.hot.accept('./print.js', function () { console.log('Accepting the updated printMe module!'); // printMe(); document.body.removeChild(ele); ele = component(); document.body.appendChild(ele); }); }
现在再修改 print.js 的打印文字并保存,通过观察控制打印结果,发现页面完成了修改并且没有产生刷新,并且点击之后控制台会出现新修改的文字. HMR
配置成功~
现在觉得 HMR
开发很难?Webpack的开发者自然考虑到了这一点,Webpack 的 loader将会帮你把这一个过程变得简单.
3.3 HRM 修改样式表
只需下载loader并完成配置,之后的同类型改动需要更新时,loader会自动在幕后通过 module.hot.accept
完成对于内容的修补.
这次实战我们先安装并配置 styleloader & cssloader, 然后借助loader的力量帮助我们实现页面样式的模块热更新,体验loader带来的便利.
- 步骤一、安装loader到项目
npm i -D style-loader css-loader
- 步骤二、加入样式对应loader配置, 完整配置代码如下所示:
webpack.config.js
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
entry: {
app: './src/index.js'
},
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
hot: true
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
inject: false,
template: 'index.html',
filename: 'index.html'
}),
new webpack.HotModuleReplacementPlugin()
]
};
- 步骤三、在项目的src文件夹下添加文件
styles.css
并在 index.js 引用:
styles.css
body {
background-color: blue;
}
index.js
import _ from 'lodash';
import printMe from './print';
import './styles.css';
function component () {
let element = document.createElement('div');
let btn = document.createElement('button');
element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
btn.innerHTML = 'Click me and check the console.';
btn.onclick = printMe;
element.appendChild(btn);
return element;
}
// document.body.appendChild(component());
let ele = component();
document.body.appendChild(ele);
if (module.hot) {
module.hot.accept('./print.js', function () {
console.log('Accepting the updated printMe module!');
// printMe();
document.body.removeChild(ele);
ele = component();
document.body.appendChild(ele);
});
}
- 步骤四、在终端输入命令
npm start
, 确认应用开启完毕后,修改styles.css
, 保存后观察控制台的打印:
body {
background-color: #fff;
}
发现页面在没刷新的情况下完成了背景颜色的变化. 借助loader的力量成功实现了 HMR
的效果.
3.4 HMR小结
Hot Module Replacement(HMR)是Webpack最棒的特性之一,当代码修完并保存之后,Webpack将重新打包项目,并将新的模块发送到浏览器端,浏览器更新对应的模块,以此达到更新应用页面的目的.
不同于实时刷新的开发工具库,HMR 在更新之后依旧能够保持原有的应用状态,提高了开发者的开发效率.
至此项目提交为 feat(project): finish dev-server & HMR config .
4. 练习3:认识loader并完成基础配置
在Webpack出现之前,前端工程师们使用的打包工具通常是 grunt 或者 gulp, 这些工具处理图片等资源的方式通常是复制,也就是将文件复制一份到打包目录下.
但是Webpack不同,它对于js和资源文件一视同仁,也就是将资源也看作模块,使用到这些模块的地方需要显示调用资源,然后由Webpack动态构建依赖图完成统一打包, Webpack通过资源间的强依赖关系,完美避开了隐式引用和无效引用造成的错误和浪费.
为了完成对任何类型资源的引用,社区出现了各种格式的loader来帮助Webpack完成这个任务. 比较通用的loader有:
- 样式loader: style-loader、css-loader等
- 图片loader: file-loader
Tip:进阶可以学习使用 pimage-webpack-loader](https://github.com/tcoopman/i... 或者 url-loader - 字体loader: file-loader
- 数据loader
以上资源可以直接放在一个控件目录下,并通过显式声明依赖建立起该控件的依赖关系图. 这样的控件更具备可移植性.
具体使用操作可以跟随官方文档的Asset Management章节跑一波,目标是认识常用loader并跟随文档完成当前项目配置即可.
至此项目提交为feat(project): finish loaders study
5. 项目地址
6. 总结
本章以开发需求探索开始, 根据总结的需求提出解决方案并选择新手友好的开发工具 webpack-dev-server
, 接着进一步了解方便开发者调试修改应用特性 HMR
, 最后再学习并使用loader完成项目的基础配置. 简单的开发环境搭建已经完成了,现在可以使用这个环境试试愉快的代码编写吧~
To be continued...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。