webpack
在 webpack 中,将所有类型文件都视为模块,然后使用不同模块加载器进行加载不同类型文件。
构建工具的历史
最开始用的工具是 grunt,配置起来稍微复杂,构建起来需要一定时间,他和 gulp 相比具体有什么不同和缺点今天已经不记得了。
gulp 可能是我用最多的 web 前端构建工具,在没有 webpack 时候,我一直在用 gulp 来构建我的 web 前端项目。gulp 也有着丰富的插件供我们来搭建应用。
不过随着 webpack 横空出世,大型的 web 前端 angular、react 和 vue 无疑都投向 webpack 怀抱。
webpack 的作者
Tobias Koppers
Koppers 背景是 c# 开发人员,javascript 只是他个人的爱好。让人惊讶的事他从来没有做过 web 应用。这就告诉我们一个道理,只有热爱的才能做好,有时候经验也不一定是好事。
Sean Thomas Larkin
说道 webpack 还不能不提这位大神人物,在许多开发者大会上都给出了 webpack 相关的分享
webpack 的实现
首先我们需要理解模块,为什么需要模块,模块化对于一门语言意味着什么呢
- 复用性
- 封装
-
可扩展(可组织)
在 javascript ,由于语言本身没有模块化的概念,但是现在 javascript 不再是操作 DOM 为页面添加交互和动画人们眼中的小跟班,已经可以独当一面的老大,所有维持这么庞大的组织,模块化是势在必行的,由于 javascript 语言本身并没有提供模块化的方案,这样就造成大家对模块化实现五花八门。
commonjs (以 nodejs 为代表)
const _ * require('lodash")
module.exports = someValue
AMD + commonJS (requirejs)
define(function(require,exports,module){
var _ require('lodash");
module.exports = someLib
});
ESM Typescript
import React, {Component} from 'react'
export default SomeComponent;
不过我们如何将这些以不同方式提供模块的库或框架整合到一起传送给浏览器呢?这的确需要有人来做这样工作,有了需求就有了实现 webpack。
Webpack 配置主要成分
Entry
所谓入口,也就是我们应用模块树的根节点,通过这个节点我们拽出我们整个树,获取树上所有节点(模块)。
module.exports = {
entry:'./src/index.js',
output:''
}
Entry 回答 webpack 将要处理那些文件的问题。
Output
Output 是回答 webpack 如何处理以及处理后放到哪里的问题。
output:{
path: path.resolve(__dirname,"build")
},
如果没有指定输出目录, webpack 默认是输出到 dist 文件夹,这里输出路径为绝对路径,所有可以使用 nodejs 的 path 模块来获取路径
Loaders
在 webpack 将所有文件视为模块,无论是 css、typescript、jsx 都视为模块,不过除了 javascript 文件以外的其他类型文件,我们需要识别并解析这些文件,才能够加载。这时候就用到了加载器 loader
{
test:regex,
use:(Arrary(String|Function))
include:RegExp[],
exclude:RegExp[],
issuer:(RegExp|String)[],
enforece:"pre"|"post"
}
webpack 提供丰富的加载器,让我们可以根据文件类型选择加载器来进行加载文件。
loader 回答 webpack 如何解释和编译成各种不同类型的文件然后将其也进行打包。
Plugins
在 webpack 中 module 和 plugin 都是非常重要的,不同项目都是通过添加不同插件或模块来完成我们想要的构建效果。plugin 可以让开发人员在 webpack 生命周期上做自己想做的事。定义一个插件也不难。
class MyFirstWebpackPlugin {
apply(compiler){
}
}
module.exports = MyFirstWebpackPlugin
- 使用起来也很方便,需要在 plugins 先 new 插件,插件可以看做类来使用。
let path = require('path');
const MyFirstWebpackPlugin = require('./build-utils/MyFirstWebpackPlugin');
module.exports = {
entry:'./src/index.js',
output:{
filename:'build.js',
path:path.resolve('./build')
},
plugins:[
new MyFirstWebpackPlugin()
],
mode:'development'
}
可以定义自己插件,通过插件可以实现一些项目中的特殊需求。
Tapable
200 行插件库,但是就是这个 200 行代码却是 webpack 插件系统的基石。
tap 做个移动项目的大家都知道 tap 是点击,还有就是水龙头的意思。Webpack 是基于事件流,内部工作流程都是基于插件机制串接起来。者有点类似 gulp 的 pipe 。插件类似于gulp 的 task。
class Compiler extends Tapable {
constructor(context) {
this.hooks = {
shouldEmit: new SyncBailHook(["compilation"]),
done: new AsyncSeriesHook(["stats"]),
additionalPass: new AsyncSeriesHook([]),
beforeRun: new AsyncSeriesHook(["compilation"]),
run: new AsyncSeriesHook(["compilation"]),
emit: new AsyncSeriesHook(["compilation"]),
afterEmit: new AsyncSeriesHook(["compilation"]),
安装 webpack
- 在 webpack 前需要先安装 nodejs,webpack 是基于 nodejs。
- 在实际开发中尽量不要全局安装 webpack,需要在项目下本地安装 webpack,避免版本冲-突。
- webpack 需要安装 webpack 同时还需要安装 webpack-cli
npm i -D webpack webpack-cli
- 在 node 8.5 以上版本引入了 npx,使用此命令执行命令的好处是他会先检查是否安装依赖,如果缺少依赖会先自动安装依赖然后再运行命令
npx webpack
在 webpack 命令下,接收零配置,无需任何配置就可以进行构建,默认会找 src 文件夹下 index.js 然后打包到 dist 文件夹下 main.js 文件。
webpack.config.js 配置 webpack
- webpack.config.js 是 node 文件,node 来运行该文件,所以规范遵循 node 的 commonjs 规范。所以需要导出一个模块,使用 commonjs 方式导出模块。
在组件中可以调用到 compiler 提供编译生命周期的回调函数,stats 上可以得到所需参数。
apply(compiler){
compiler.hooks.done.tapAsync("MyFirstWebpackPlugin",(stats,cb)=>{
console.log(stats)
cb();
})
}
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins:[
new HtmlWebpackPlugin({
}),
new MyFirstWebpackPlugin()
],
在开发过程中,可以将 js 内存中进行打包,以提高在开发过程中的性能。在开发时可以使用
wepack-dev-server
然后会运行一个服务来运行我们所指定文件夹下的文件。
"scripts": {
"build": "webpack",
"start": "webpack-dev-server"
},
web 开发过程中通常需要一个入口引导页 index.html 来引入 bundle.js 然后启动我们应用。
所以可以在 src 文件夹下建立一个 index.html 作为入口 index.html 文件。
webpack 插件可以帮助我们来完成将 html 打包到 build 下可以自动引入生成的 js 文件。
npm install -D html-webpack-plugin -D
引入这个插件作用是可以将 html 部署到项目下,然后会根据配置自动加载 bundle.js 文件。
new HtmlWebpackPlugin({
template:"./src/index.html"
}),
通过 HtmlWebpackPlugin 可以修改 index.html 的标题,替换模板中的变量是遵循 ejs 的语法
<%=htmlWebpackPlugin.options.title%>
在定义插件时候可以通过 title 将值传入到 options.title
new HtmlWebpackPlugin({
template:"./src/index.html",
title:"zidea"
}),
zidea
new HtmlWebpackPlugin({
template:"./src/index.html",
title:"zidea",
minify:{
removeAttributeQuotes:true,
collapseWhitespace:true
}
}),
通过将 HtmlWebpackPlugin 中配置 hash 值设置为 true,可以就在 build js后面添加 hash 值以便清除其在浏览器中的缓存。
hash:true
然后生成文件为 build.js?
插件清楚项目
npm install clean-webpack-plugin -D
构建多入口应用
entry:{
index:'./src/index.js',
foo:'./src/foo.js'
},
通过在 HtmlWebpackPlugin 的配置可以打包出多个 html 文件,每一个 html 文件引用不同的 js 文件
new HtmlWebpackPlugin({
template:"./src/index.html",
filename:'index.html',
title:"zidea",
hash:true,
chunks:['index']
}),
new HtmlWebpackPlugin({
template:"./src/index.html",
filename:'about.html',
title:"zidea",
hash:true,
chuncks:['foo']
}),
- chucks 指定该文件引用 JavaScript 文件
热更新
可以通过配置 devServer 来实现热更新,好处是当我们在开发模式一遍修改代码无需自动重新更新也就是所谓强刷就,就能将修改内容更新到界面。
devServer:{
port:3000,
contentBase:path.join(__dirname,'build'),
compress:true,
open:true,
hot:true
},
-在 devServer 将 hot 属性值设置为 true
- 引入 HotModuleReplacementPlugin() 插件
new webpack.HotModuleReplacementPlugin(),
- 在文件中指定热更新的位置
if(module.hot){
module.hot.accept('./foo.js',()=>{
let add = require('./foo.js');
document.getElementById('app').innerHTML = add(1,2);
console.log(add(1,2));
})
}
- 也就是这个文件中引用 foo.js 当 foo.js 被修改,就会触发 accept 方法中回调函数来重新引用修改过的文件,从而实现热更新。
加载样式文件
npm install style-loader css-loader less less-loader -D
body{
background-color: lightblue;
}
import add from './foo.js';
import './style.css';
module:{
rules:[
{test:/\.css$/,use:['style-loader','css-loader']}
]
},
抽离样式
如果我们在产品将所有样式都打入 bundle.js bundle.js 体积变得庞大,延长加载时间,影响用户体验这不是我们像看到的,我们需要将 css 文件单独打包后进行部署。这时候我们就用到了 extract-text-webpack-plugin 这个插件,webpack 扩展都是依靠插件,这个插件对应于 webpack4 我们需要在其后加上@next。
npm install extract-text-webpack-plugin@next mini-css-extract-plugin
然后将其引用
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
使用与之前我们使用插件略有不同,需要两个地方他们分别是
- 在加载器中使用表示将那些加载过来文件进行抽离,就是在 loader use 外面套上一层。
- 然后 plugins 中进行使用。
module:{
rules:[
{
test:/\.css$/,
use: ExtractTextWebpackPlugin.extract({
use: [
{loader:'css-loader'}
]
}
)
}
]
},
plugins:[
new ExtractTextWebpackPlugin({
filename:'css/index.css'
}),
new webpack.HotModuleReplacementPlugin(),
new CleanWebpackPlugin({
}),
new HtmlWebpackPlugin({
template:"./src/index.html",
filename:'index.html',
title:"zidea",
hash:true,
}),
new MyFirstWebpackPlugin()
],