Webpack-基础概念
深入浅出Webpack学习笔记
基本概念
常用的构建工具
所有的构建工具所做的工做大致一样,都是把源代码翻译转换成可执行的代码,包括如下内容:
- 代码转换:TypeScript转换成JavaScript,SCSS转换成CSS;
- 文件优化:压缩JavaScript、CSS、图片等资源,利用一些优化手段,如摇树优化,移除无关代码;
- 代码分割:提取多个页面的公共代码、提取首屏不需加载的代码让其异步加载,防止首次进入应用等待时间过长;
- 模块合并:在一些模块化的项目中会有很多个模块和文件,需要构建工具把这些模块文件分类合并成一个文件;
- 自动刷新:监听本地代码,自动构建加载,方便开发;
- 代码校验:提交代码前进行代码规范检查;
- 自动发布:更新完代码后,自动构建出线上代码,推送到线上或其他环境上;
Npm Script
Grunt
Gulp
Fis3
Webpack
https://www.webpackjs.com/
Webpack是一个模块化打包工具,专注于构建模块化项目,在Webpack眼里一切文件都是模块,通过Loader转换翻译文件,通过Plugin注入钩子,最后输出由多个模块组合成的文件。
之所以一切文件皆模块,如:JavaScript、CSS、SCSS以及图片等资源,在Webpack眼中都是模块,因为这样可以更好的理清描述各个模块之间的依赖关系,方便Webpack对模块进行打包组合,输出浏览器使用的静态资源。
简单使用:
module.exports = {
// 定义入口文件
entry: './index.js',
// 定义打包输出文件
output: {
// 最终会把依赖的所有模块打包成一个bundle.js文件
filename: './bundle.js'
}
}
Webpack的优点
- 专注于处理模块化项目,可以做到开箱即用,一步到位;
- 通过Plugin进行扩展,完整好用又不失灵活;
- 使用场景丰富,除了web端,其他场景也可以;
- 社区活跃;
- 开发体验好;
缺点是只能采用模块化开发项目。
Rollup
安装与使用
安装只需要一行命令,当然可以全局安装,但是不推荐。
npm install -D webpack
或者指定版本号:
npm install -D webpack@2.xxx
或者直接安装最新版:
npm install -D webpack@beta
注意:如果你使用的webpack版本较新,在webpack4.x测试下,你需要额外安装一依赖:
npm i webpack-cli @webpack-cli/init
。 可以参考webpack-cli
运行Webpack命令:
node_modules/.bin/webpack
或者通过配置npm script来运行:
"script": {
"start": "webpack --config webpack.config.js"
}
具体如何使用?
前面有了基本使用方法,但是具体落实到代码上该怎么写?我们可以通过构建一个采用CommonJs模块化的简单Demo来理解。
建立如下文件:
|-- index.html // 入口文件
|-- show.js // js文件,里面我们随便写一个函数
|-- main.js // 入口文件
|-- package.json // npm 配置文件
|-- webpack.config.js // webpack 配置文件
index.html
index.html
文件内容包含了一个script和一个id等于app的div。
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<div id="app"></div>
<!-- 导入 Webpack 输出的 JavaScript 文件 -->
<script src="./dist/bundle.js"></script>
</body>
</html>
show.js
show.js
文件定义了一个show
函数,该方法将给页面中的div插入一段文本;同时,我们利用CommonJs规范,将该函数导出。
function show() {
document.getElementById('app').innerText = 'hello world';
}
module.exports = show;
main.js
main.js
文件将show.js
引入,并执行show
函数。
const show = require('./show.js');
show();
webpack.config.js
执行webpack构建执行命令的时候,会自动读取项目根目录下的webpack.config.js
文件,所以我们新建该文件,并指明入口文件和打包输出文件。
const path = require('path');
module.exports = {
entry: './main.js',
output: {
filename: './bundle.js',
path: path.resolve(__dirnam, './dist') // 输出路径
}
}
之所以使用CommonJs规范来导出webpack配置,是因为webpack运行在Node下,所以我们要使用CommonJs规范来描述一个如何构建的Object对象。
执行webpack构建命令后,在项目根目录下会多出一个dist文件夹,以及一个bundle.js文件。bundle.js
依赖main.js
和show.js
两个文件以及内置的webpackBootstrap
启动函数,从入口文件main.js
出发,识别出源码中模块化导入的语句,把入口文件所依赖的模块或文件递归的打包到一个文件中:bundle.js文件。
此时,直接打开index.html
文件可以正常显示一段文案。
使用Loader
继续前面的内容,这次我们创建一个CSS文件: main.css
。
建立如下文件:
|-- index.html // 入口文件
|-- show.js // js文件,里面我们随便写一个函数
|-- main.js // 入口文件
|-- package.json // npm 配置文件
|-- webpack.config.js // webpack 配置文件
|-- main.css
文件中我们添加一段文本居中的样式:
#app {
text-align: center;
}
然后,我们在main.js
引入这个CSS文件:
// 引入css
require('./main.css');
const show = require('./show.js');
show();
编写工作做完后,我们自然的想到直接执行webpack构建命令,但是此时还不可以,因为Webpack原生仅支持解析JavaScript文件,如果需要
解析其他类型的文件,需要引入相应的Loader,这里,我们因为需要解析CSS,所以需要引入CSS Loader。
手动的去配置webpack.config.js
文件:
const path = require('path');
module.exports = {
entry: './main.js',
output: {
filename: './bundle.js',
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
// 用正则匹配css文件
test: '/\.css$/',
use: ['style-loader', 'css-loader?minimize'], // minimize:需要进行压缩
}
]
}
}
上面我们简单的配置了一个Loader规则。
Loader相当于一个翻译员,将某个文件源码翻译成可执行的代码。配置规则要求我们在rules
数组中配置一个对象,指定test
属性值来匹配那些文件需要翻译,通过use
来指定需要使用哪些Loader,这里我们使用了style-loader
和css-loader
。
需要注意的是,在配置use
属性的时候:
-
use
属性值是一个数组,数组中的每个元素为loader的名字,尤其要注意的是,Loader的执行顺序是由后到前; - 可以给Loader以URL querystring的形式传递参数,比如前面的
css-loader?minimize
,具体参可以参考所使用的Loader文档;
理解了Loader后,我们需要进行安装相应的Loader依赖:
npm install -D style-loader css-loader
所有准备工作做完后,我们执行构建命令:
npm start
或者 node_modules/.bin/webpack
然后再观察bundle.js
文件,会发现代码更新了,并且CSS代码也被打包了进来,打开index.html,可以看到居中效果。
这里我们提一下,CSS之所可以写在JavaScript中,归功于刚才引入的style-lader
,大概远离就是将CSS样式以字符串的形式存储到JavaScript对象中,然后在网页执行的时候,通过DOM操作动态的加入到页面中的<style>
标签中。
当然,这样会导致页面加载时间变长,一定程度上需要我们再去优化处理,比如将CSS单独打包成一个文件,单独的输出,这种操作,我们可以通过Plugin来实现。Plugin也是Webpack的一个重要概念。
Tips:
use
的配置中,给Loader传递参数除了刚才的写法,我们还可以传递一个对象来实现:
module.exports = {
rules: [
{
test: '/\.css$/',
use: ['style-loader', { loader: 'css-loader', options: { minimize: true } }],
}
]
}
除了在webpack.config.js
中配置Loader外,还可以在代码文件中直接引入相关Loader,比如刚才的场景就可以这么处理:
// main.js
requrie('style-loader!css-loader?minimize!.main.css');
这样就能指定对引入的main.css
文件先进行css-loader
在采用style-loder
转换。
另外,前面我们提到了Loader的记载顺序是从后到前的,所以这里我们必须把css-loader
放在后面,也就是先执行。因为css-loder
是将css代码编译,而style-loader
是将编译好的css加到页面中。
使用Plugin
Plugin是用来扩展Webpack功能的,给Webpack带来了很大的灵活性,通过在构建流程中注入钩子来实现。
继续前面的操作,我们这次需要优化一下,把main.css
代码打包到单独的一个文件中。
我们需要在配置文件webpack.config.js
文件中添加plugins
属性,来配置Plugin。
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: 'main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: '/\.css$/',
use: ExtractTextPlugin({
use: ['css-loader']
})
}
]
},
plugins: [
new ExtractTextPlugin({
// 从.js中提取.css文件
filename: `[name]_[contenthash:8].css`
})
]
}
前面我们引入了新的插件,需要先安装:
npm install -D extract-text-webpack-plugin
然后我们执行构建命令,会发现dist目录下多出来一个.css
结尾的CSS文件,bundle.js中也没有CSS代码了,然后我们手动将该CSS文件引入index.html
就可以了。
通过上面的代码我们可以看到,我们可以通过配置plugins
属性来配置,其值是一个数组,数组中的每一项是一个实例,并且在实例化一个对象的时候,我们可以通过构造函数传入这个组件支持的属性配置。
上面用到的extract-text-webpack-plugin
就是一个插件,用来提取JavaScript中的CSS代码到一个单独的文件,filename
属性指定了输出的文件名,[name]_[contenthash:8].css
中name
代表文件名,contenthash:8
意思是根据文件内容算出8位hash值。
该插件的其他配置可以在官网上找到。
使用DevServer
到目前为止,我们也只是做了打包构建的工作,在正常的开发过程中,还需要实现下面的功能:
- 代码自动构建,自动刷新,实现文件变化监听;
- 提供HTTP服务;
- 支持Source Map,方便调试。
上面提到的,Webpack原生支持1、3两点,对于提供HTTP服务,我们可以借助DevServe,是官方提供的一个开发工具。
DevServer会自动开启一个本地HTTP服务,同时会自动启动Webpack构建,并通过WebSocket协议接受Webpack的文件的实时变更,做到可以实时预览,方便我们开发。
安装与启动
安装DevServer:
npm install webpack-dev-server
启动DevServer
webpack-dev-server
启动成功后,我们可以在控制台看到一串输出:
Project is running at http://localhost:8080
此时,我们访问http://localhost:8080
就会自动执行根目录下的index.html
文件。
如果此时访问,我们会发现引入的bundle.js
报404错误,是因为DevServer会把Webpack构建的文件保存在内存中,在要访问输出的文件时候,必须通过HTTP服务来访问,并且DevServer不会理会webpack.config.js
配置里的output.path
属性,所以我们需要访问http://localhost/bundle.js
才可以。
修改index.html
文件js引用路径:
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<div id="app"></div>
<!-- 修改路径如下 -->
<script src="./bundle.js"></script>
</body>
</html>
实时预览
我们现在修改main.js
、main.css
、show.js
文件中的任一一处,保存后,浏览器便会自动刷新,加载修改后的代码。
不过我们需要注意的是,通过DevServer启动的Webpack会自动开启文件监听,也就是这里的修改代码自动触发刷新页面的功能;而如果我们通过webpack
来启动默认是不会开启监听模式的,只有我们显示的指明需要开启监听模式才可以。
开启监听模式: webpack --watch
DevServer会让Webpack在构建的过程中在JavaScript代码中注入一个代理客户端用于控制网页,并通过WebSocket协议进行通知,如果文件发生变化,会立刻告知刚才注入的代理客户端,代理客户端收到信息后,执行刷新网页操作。
但是如果我们修改index.html
文件不会触发网页刷新操作,这是因为Webpack在启动时候会以配置中心的entry为口入去递归解析entry所以来的文件,只有entry本身和其所依赖的文件才会被添加到监听对象中;另外,index.html文件脱离了JavaScript模块化系统,所以Webpack监听不到。
模块热替换
模块热替换不同于前面的页面刷新,这里的模块热替换,可以在不刷新页面的情况下实现重新加载新的模块代码的效果,当有新的模块代码时候,会将新的替换掉老的,并重新执行一遍代码,从而做到不刷新页面,却可以实时预览的效果。
相比较来说,模块热更新在开发体验和效率上会略胜一筹。
模块热替换默认是关闭的,我们可以在启动DevServer的时候带上--hot
参数来开启。
Source Map
通过指定
devtool source-map
参数来开启Source Map功能。
所谓的Source Map就是一份代码映射。在开过过程中,我们在浏览器看到的代码都是编译过的,所以没办法看到未编译的代码,很难去调试,代码可读性很差。
而Source Map可以将编译前的代码给映射出来,让我们可以在源码上调试。Webpack支持生成Source Map,只需要在启动的时候带上--devtool source-map
参数。然后启动后,我们便可以在Chrome开发者工具下调试。
核心概念
- Entry: 入口配置,Webpack构建的第一步将从Entry开始,可以抽象成输入;
- Module:在Webpack里一切皆模块,一个模块对应一个文件。Webpack会从Entry入手,递归的找到所有的依赖模块;
- Chunk:代码块,一个Chunk由多个模块组合而成,用于代码分割片段;
- Loader:模块转换器,用于把模块中的内容按需求转换成新的内容,如ES6转换成ES5;
- Plugin:扩展插件,在Webpack构建流程中特定时机注入扩展来改变逻辑和结果;
- Output:输出结果,在Webpack经过前面一系列处理后返回的最终结果。
Webpack启动后会从Entry配置的Module开始,递归的解析其依赖的所有Module,每找到一个Module,会调用相应的Loader对其进行转换,对Module转换后,在解析当前Module所依赖的Module,同样会调用相应的Loader。这些Module会以Entry为单位分组,一个Entry和其依赖的所有Module都会打包成一个Chunk。最终Webpack会把Chunk转换成文件输出,整个构建流程中,Webpack会在特定时机执行Plugin定义的逻辑。