开发工具诞生的目的永远是加速开发. 程序员应该不断追求更快更好的开发工具.
前文一步步学Webpack4(0)-- 实战起步已经完成了Webpack环境的搭建以及实现了一句命令自动打包项目,这一次咱们继续使用以前的项目webpack-stepbystep来尝试搭建适合对开发者友好的项目开发环境.css
本章按照如下步骤进行:html
写Webpack文章不写版本都是耍流氓,这篇文章基于当下最新的 webpack v4.22.0 以及 webpack-cli v3.1.2 编写.前端
Eating your own dog food
尝试深刻探索学习Webpack的人大概都有一颗想给本身写个顺手的手脚架的心吧,吃本身的狗粮这件事对开发者确定是好事,可是前提是本身真正懂得本身的需求.webpack
对于一个普通前端开发者来讲,一个简单项目的手脚架必须具有必定的能力,总结一下一些必不可少的需求吧:git
接下来咱们就来借助Webpack的能力,一个个实现这些需求~程序员
source map
实现调试信息追溯文章跟随 一步步学Webpack4(0)-- 实战起步 继续开发.github
项目已经可以使用Webpack打包了,咱们如今使用这个项目来随便写点会发生错误的代码,例如在方法第一行加入 console.abg('generate component')
:web
index.jsnpm
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
完成打包,运行结果以下:json
发现错误是被指向了编译后的文件 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
还有许多配置能够选择, 不过与本章的学习关系不大, 先继续往下学习吧~
刀耕火种时期每次保存完代码都要F5,在项目中应用了Webpack以后每次保存完代码竟然须要先Webpack打包再F5,这么愚蠢的事情程序员怎么可能容许呢,因而开发工具们开始诞生了:
严格来讲这不算是一种额外的开发工具,这只是Webpack的一种运行模式,能够在终端输入 webpack --watch
开始持续监听文件变化,只要修改代码并保存,webpack将会自动帮你打包项目,听起来还不错可以自动打包,可是这种模式并不能帮助开发者更新页面内容也就是说, 你仍是须要本身按F5刷新..., 感受仍是有点惨啊.
算了 =。= Next one
这是一个官方推荐的新手友好的开发工具,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结合可以进行更多的自定义配置,不过暂时咱们不须要用到它.
完成上一小节的配置以后,咱们能够开始尝试在当前项目中编写代码了,首先咱们固然是先来改正第一小节的错误,将 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)!
说了那么多,不如show me your code. 好,如今立刻经过实战来见识一下 HMR
的厉害.
咱们新建一个模块称为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());
修改 print.js
的打印内容并保存,当前效果是:整个页面直接经过刷新来更新界面.
步骤一:首先更新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(); }); }
步骤四:你觉得就这样结束了?其实并无,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将会帮你把这一个过程变得简单.
只需下载loader并完成配置,以后的同类型改动须要更新时,loader会自动在幕后经过 module.hot.accept
完成对于内容的修补.
此次实战咱们先安装并配置 styleloader & cssloader, 而后借助loader的力量帮助咱们实现页面样式的模块热更新,体验loader带来的便利.
npm i -D style-loader css-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() ] };
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
的效果.
Hot Module Replacement(HMR)是Webpack最棒的特性之一,当代码修完并保存以后,Webpack将从新打包项目,并将新的模块发送到浏览器端,浏览器更新对应的模块,以此达到更新应用页面的目的.
不一样于实时刷新的开发工具库,HMR 在更新以后依旧可以保持原有的应用状态,提升了开发者的开发效率.
至此项目提交为 feat(project): finish dev-server & HMR config .
在Webpack出现以前,前端工程师们使用的打包工具一般是 grunt 或者 gulp, 这些工具处理图片等资源的方式一般是复制,也就是将文件复制一份到打包目录下.
可是Webpack不一样,它对于js和资源文件一视同仁,也就是将资源也看做模块,使用到这些模块的地方须要显示调用资源,而后由Webpack动态构建依赖图完成统一打包, Webpack经过资源间的强依赖关系,完美避开了隐式引用和无效引用形成的错误和浪费.
为了完成对任何类型资源的引用,社区出现了各类格式的loader来帮助Webpack完成这个任务. 比较通用的loader有:
以上资源能够直接放在一个控件目录下,并经过显式声明依赖创建起该控件的依赖关系图. 这样的控件更具有可移植性.
具体使用操做能够跟随官方文档的Asset Management章节跑一波,目标是认识经常使用loader并跟随文档完成当前项目配置便可.
至此项目提交为feat(project): finish loaders study
本章以开发需求探索开始, 根据总结的需求提出解决方案并选择新手友好的开发工具 webpack-dev-server
, 接着进一步了解方便开发者调试修改应用特性 HMR
, 最后再学习并使用loader完成项目的基础配置. 简单的开发环境搭建已经完成了,如今可使用这个环境试试愉快的代码编写吧~
To be continued...