一切皆模块:
一个Web工程通常会包括HTML、CSS、JS、模板、图片、字体等多种类型的静态资源,并且这些资源之间都存在着某种联系。比如:JS文件之间有互相依赖的关系,在CSS中可能会引用图片和字体等。对于Webpack来说,所有这些静态资源都是模块,可以像加载一个JS一样去加载它们,比如在Index.js中加载style.css:import './style.css'
。
静态资源:使用静态网页技术(HTML、CSS、JS等)开发的资源。如HTML、CSS、JS、文本、图片、音频、视频等都属于静态资源。所有用户访问得到的结果都一样。
动态资源:是从资源的服务器数据库里拿出来,使用动态网页技术(JSP、PHP、ASP等)发布的资源。不同用户访问,得到的结果可能不一样。
loader概述:
Webpack本身只能接受JavaScript,为了使其能够处理其他类型的资源,必须使用loader将资源转译为Webpack能够理解的形式。
每个loader本质上都是一个函数,用公式表达loader的本质则为以下形式:output = loader(input)
。 例如:当使用babel-loader将ES6+的代码转为ES5时,上面的公式如下:ES5 = babel-loader(ES6+)
。
loader可以是链式的,可以对一种资源设置多个loader,第一个loader的输入是文件源码,之后所有loader的输入都为上一个loader的输入,最后一个loader则直接输出给Webpack。用公式表达则为以下形式:output = loaderA(loaderB(input))
。例如,对于SCSS类型的资源来说,需要sass-loader来处理其语法,并将其编译为CSS;接着再用css-loader处理CSS的各类加载语法;最后使用style-loader来讲样式字符串包装成style标签插入页面,因此需要如下loader:Style标签 = style-loader(css-loader(sass-loader(SCSS)))
。
loader使用:
- 在src目录下新建style.css,在index.js中引入。
- 在工程目录下npm安装css-loader:
npm install css-loader style-loader
。 - 在webpack.config.js中进行如下配置。
- 此时再进行打包,样式就会生效了。
css-loader的作用仅仅是处理CSS的各种加载语法(@import和url()函数等),如果要使样式起作用还需要style-loader来把样式插入页面。
css-loader和style-loader通常是配合在一起使用的。
loader配置:
loader都是一些第三方npm模块,Webpack本身并不包含任何loader,因此使用loader的第一步就是先从npm安装它。在配置loader时,实际上定义的是模块规则(module.rules),它主要关注两件事:该规则对哪些模块生效(test、exclude、include等),使用哪些loader(use配置)。loader可以是链式的,并且每一个都允许拥有自己的配置项。
与loader相关的配置都在module对象中,其中module.rules代表了模块的处理规则,每条规则内部都可以包含很多配置项:
-
test:可接收一个正则表达式或者一个元素为正则表达式的数组,只有正则匹配上的模块才会使用这条规则。
-
use:可接收一个数组,数组包含该规则所使用的loader。在Webpack打包时是按照数组从后往前的顺序讲资源交给loader处理的,因此要把最后生效的放在前面。
loader作为预处理器通常会提供一些配置项,在引入loader的时候可以通过options将它们传入,有些loader可能使用query来代替options,从功能上来说它们并没有太大的区别,具体参考loader本身的文档。
use:[ { loader:'css-loader', options:{} } ]
-
exclude和include:用来排除或包含指定目录下的模块,可接收正则表达式或字符串(文件绝对路径),以及由它们组成的数组。exclude和include同时存在时,exclude的优先级更高。该配置项通常是必加的,否则可能拖慢整体的打包速度。
rules:[ { test:/\.css$/, use:['style-loader','css-loader'], exclude:/src\/lib/, include:/src/ } ]
该规则仅对src目录生效,但是排除其中的src/lib目录。
-
resource和issuer:可用于更加精确地确定模块规则的作用范围。在Webpack中,认为被加载模块是resource,加载者是issuer。test、exclude、include本质上属于对resource也就是被加载者的配置。
rules:[ { test:/\.css$/, use:['style-loader','css-loader'], issuer:{ test: /\.js$/, include: /src/ } } ]
只有src目录下的JS文件引用CSS文件,这条规则才会生效。
上面的配置虽然实现了需求,但是test、exclude、include这些配置项分布于不同的层级上,可读性较差,可以将它改为另一种等价的形式。
rules:[ { use:['style-loader','css-loader'], resource:{ test:/\.css$/ }, issuer:{ test: /\.js$/, include: /src/ } } ]
-
enforce:enforce用来指定一个loader的种类,只接收pre或post两种字符串类型的值。如果某一个loader是需要在所有loader之前执行的,指定其enforce为pre;如果某一个loader是需要在所有loader之后执行的,指定其enforce为post。
Webpack中的loader安装执行顺序可分为pre、inline、normal、post四种类型,之前直接电仪的loader都属于normal类型,inline形式官方已经不推荐使用,而pre和post则需要使用enforce来指定。
rules:[ { test:/\.js$/, enforce:'pre', use:'eslint-loader' } ]
在配置中添加了一个eslint-loader来对源代码进行质量检测,其enforce的值为pre,代表它将在所有正常loader之前执行,这样可以保证其检测的代码不是被其他loader更改过的。
常用loader:
babel-loader:
babel-loader用来处理ES6+并将其编译为ES5,它使我们能够在工程中使用最新的甚至还在提案中的语言特性,同时不必特别关注这些特性在不同平台的兼容问题。
babel-loader支持从.babelrc文件读取Babel配置,因此可以将相关配置从Webpack的配置文件中提取出来。
-
安装:
npm install babel-loader @babel/core @babel/preset-env
。各个模块的作用如下:
- babel-loader:它是使Babel与Webpack协同工作的模块。
- @babel/core:它是Babel编译器的核心模块。
- @babel/preset-env:它是Babel官当推荐的预置器,可根据用户设置的目标环境自动添加所需的插件和补丁来编译ES6+代码。
-
在webpack.config.js中进行如下配置:
rules:[ { test:/\.js$/, exclude:/node_modules/, use:{ loader: 'babel-loader', options: { cacheDirectory: true, presets: [[ 'env', {modules: false} ]] } } } ]
- 由于babel-loader通常属于对所有JS后缀文件设置的规则,所以需要在exclude中添加node_mudules,否则会令babel-loader编译其中所有的模块,这将严重拖慢打包的速度,并且有可能改变第三方模块的原有行为。
- 对于babel-loader本身添加了cacheDirectory配置项,它会启用缓存机制,在重复打包未改变过的模块时防止二次编译,同样也会加快打包的速度。cacheDirectory可以接受一个字符串类型的路径来作为缓存路径,这个值也可以为true,此时其缓存目录会指向node_modules/cache/babel-loader。
- 由于@babel/preset-env会将ES6 Module转化为CommonJS的形式,这会导致Webpack中的tree-shaking特性失效。将@babel/preset-env的modules配置项设置为false会禁用模块语句的转化,而将ES6 Module的语法交给Webpack本身处理。
ts-loader:
ts-loader是用于连接Webpack与Typescript的模块。
- 安装:
npm install ts-loader typescript
。 - 在webpack.config.js进行如下配置:
rules:[ { test: /\.ts$/, use: 'ts-loader' } ]
- Typescript本身的配置并不在ts-loader中,而是必须要放在工程目录下的tsconfig.json中。
{ "compilerOptions": { "target": "es5", "sourceMap": true } }
html-loader:
html-loader用于将HTML文件转化为字符串并进行格式化,这使得可以把一个HTML片段通过JS加载进来。
//header.html
<header>
<h1>This is a Header.</h1>
</header>
//index.js
import headerHtml from ‘./header.html’
document.write(headerHtml )
header.html将会转化为字符串,并通过document.write()插入页面中。
- 安装:
npm install html-loader
。 - 在webpack.config.js进行如下配置:
rules:[ { test: /\.html$/, use: 'html-loader' } ]
handlebars-loader:
handlebars-loader用于处理handlebars模块,在安装时要额外安装handlebars。
//content.handlebars
<div class="entry">
<h1>{
{title}}</h1>
<div class="body"<{
{body}}</div>
</div>
//index.js
import contentTemplate from './content.handlebars'
const div = document.createElement('div');
div.innerHTML = contentTemplate({
title:'Title',
body:'Your books are due next Tuesday'
})
document.body.appendChild(div)
handlebars文件加载后得到的是一个函数,可以接受一个变量对象并返回最终的字符串。
- 安装:
npm install handlebars-loader handlebars
。 - 在webpack.config.js进行如下配置:
rules:[ { test: /\.handlebars$/, use: 'handlebars-loader' } ]
file-loader:
file-loader用于打包文件类型的资源,并返回其publicPath。
- 安装:
npm install file-loader
。 - 在webpack.config.js进行如下配置:
const path = require('path'); modules: { entry: './app,js', output: { path: path.join(__dirname,'dist'), filename: 'bundle.js' }, module: { rules:[ { test: /\.(png | jpg | gif)$/, use: 'file-loader' } ] } }
上面对png、jpg、gif这类图片资源使用file-loader,然后就可以在JS中加载图片了。
import avatarImage from './avatar.jpg';
console.log(avatarImage ); //xxx.jpg
output.path是资源的打包输出路径,output.publicPath是资源引用路径。使用Webpack打包完成后,dist目录下会生成名为xxx.jpg的图片文件。由于配置中并没有指定output.publicPath,因此这里打印出的图片路径只是文件名,默认为文件的hash值加上文件后缀。
const path = require('path');
modules: {
entry: './app,js',
output: {
path: path.join(__dirname,'dist'),
filename: 'bundle.js',
publicPath: './assets'
},
module: {
rules:[
{
test: /\.(png | jpg | gif)$/,
use: 'file-loader'
}
]
}
}
此时图片路径会成为以下形式:./assets/xxx.jpg。
file-loader也支持配置文件名以及publicPath(这里的publicPath会覆盖原有的output.publicPath),通过loader的options传入。
rules:[
{
test: /\.(png | jpg | gif)$/,
use:{
loader: 'file-loader',
options:{
name: '[name].[ext]',
publicPath: './another-path/'
}
}
}
]
上面的配置会使图片路径成为如下形式:./another-path/avatar.jpg。
url-loader:
url-loader与file-loader作用类似,唯一的不同在于用户可以设置一个文件大小的阈值,当大于该阈值时与file-loader一样返回publicPath,而小于该阈值时则返回文件base64形式编码。
- 安装命令:`npm install url-loader’。
- 在webpack.config.js进行如下配置:
rules: [ { test: /\.(png | jpg | gif)$/, use: { loader: 'url-loader', options: { limit: 10240, name: '[name].[ext]', publicPath: './assets-path/' } } } ]
vue-loader:
vue-loader用于处理vue组件。vue-loader可以将组件的模板、JS样式进行拆分。
//App.vue
<template>
<h1>{
{title}}</h1>
</template>
<script>
export default {
name: 'app',
data () {
return {
title: 'Welcome'
}
}
}
</script>
<style lang="css">
h1{
color: #09c;
}
</style>
- 安装命令:
npm install vue-lodaer vue vue-template-compiler css-loader
。在安装时,除了必要的vue与vue-loader以外,还要安装vue-template-compiler来编译Vue模板,以及css-loader来处理样式(如果使用SCSS或LESS则仍需要对应的loader)。
- 在webpack.config.js进行如下配置:
rules: [ { test: /\.vue$/, use: 'vue-loader' } ]
自定义loader:
loader初始化:
将实现一个loader,它会为所有JS文件启用严格模式,也就是说,它会在文件头部加上如下代码:use strict
。
- 创建一个force-strict-loader文件夹,然后在该文件夹下执行npm初始化命令:
npm init -y
,会生成package.json文件。
- 创建index.js,也就是loader的主体。
- 然后就可以在Webpack工程中安装并使用这个loader了,安装命令:
npm install xx/force-strict-loader
。在Webpack工程目录下使用相对路径安装,会在项目的node_modules中创建一个指向实际force-strict-loader目录的软链,也就是说之后可以随时修改loader源码并且不需要重复安装了。
4. 在webpack.config.js进行如下配置:
rules: [ { test: /\.js$/, use: 'force-strict-loader' } ]
5. npm run build
重新进行打包,就可以看到所有JS文件的头部都已经加上了启用严格模式的语句。
启用缓存:
当文件输入和其依赖没有发生变化时,应该让loader直接使用缓存,而不是重复进行转换的工作。通过启用缓存可以加快Webpack打包速度,并且可保证相同的输入产生相同的输出。可以使用this.cacheable进行控制。
- 修改loader;
module.exports = function(content, sourceMap){ if(this.cacheable){ this.cacheable(); } var useStrictPrefix = '\'use strict\';\n\n'; return useStrictPrefix+content; };
获取options:
loader的配置项通过use.options传进来。
- 在webpack.config.js进行如下配置:为force-strict-loader传入一个配置项sourceMap;
rules: [ { test: /\.js$/, use: { loader: 'force-strict-loader', options: { sourceMap: true } } } ]
- 接下来要在loader中获取它:
- 首先要安装一个依赖库loader-utils,它主要用于提供一些帮助函数。在force-strict-loader目录下执行以下命令:
npm install loader-utils
; - 接着更改loader;
var loaderUtils = require("loader-utils"); module.exports = function(content, sourceMap){ if(this.cacheable){ this.cacheable(); } //通过loaderUtils.getOptions()可以获取到配置对象 var options = loaderUtils.getOptions(this) || {}; console.log(options ); var useStrictPrefix = '\'use strict\';\n\n'; return useStrictPrefix+content; };
- 首先要安装一个依赖库loader-utils,它主要用于提供一些帮助函数。在force-strict-loader目录下执行以下命令:
source-map:
source-map可以便于实际开发者在浏览器控制台查看源码。如果没有对source-map进行处理,最终也就无法生成正确的map文件,在浏览器的dev tool中可能就会看到错乱的源码。
- 修改loader;
var loaderUtils = require("loader-utils"); var SourceNode = require("source-map").SourceNode; var SourceMapConsumer = require("source-map").SourceMapConsumer; module.exports = function(content, sourceMap){ var useStrictPrefix = '\'use strict\';\n\n'; if(this.cacheable){ this.cacheable(); } //通过loaderUtils.getOptions()可以获取到配置对象 var options = loaderUtils.getOptions(this) || {}; // 支持source-map的情况 if(options.sourceMap && sourceMap){ var currentRequest = loaderUtils.getCurrentRequest(this); var node = SourceNode.fromStringWidthSourceMap( content, new SourceMapConsumer(sourceMap) ); node.prepend(useStrictPrefix); var result = node.toStringWidthSourceMap({file:currentRequest}); var callback = this.async(); callback(null,result.code,result,map,toJSON()); } //不支持source-map的情况 return useStrictPrefix+content; };
首先,在loader函数的参数中获取到sourceMap对象,这是由Webpack或者上一个loader传递下来的,只有当它存在时我们的loader才能进行继续处理和向下传递。
之后,通过source-map这个库来对map进行操作,包括接收和消费之前的文件内容和source-map,对内容节点进行修改,最后产生新的source-map。
在函数返回的时候要使用this.async获取callback函数,主要是为了一次性返回多个值。callback函数的3个参数分别是抛出的错误、处理后的源码以及source-map。