《四》Webpack预处理器Loader

一切皆模块:

一个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使用:

  1. 在src目录下新建style.css,在index.js中引入。
    在这里插入图片描述
    在这里插入图片描述
  2. 在工程目录下npm安装css-loader:npm install css-loader style-loader
  3. 在webpack.config.js中进行如下配置。
    在这里插入图片描述
  4. 此时再进行打包,样式就会生效了。

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代表了模块的处理规则,每条规则内部都可以包含很多配置项:

  1. test:可接收一个正则表达式或者一个元素为正则表达式的数组,只有正则匹配上的模块才会使用这条规则。

  2. use:可接收一个数组,数组包含该规则所使用的loader。在Webpack打包时是按照数组从后往前的顺序讲资源交给loader处理的,因此要把最后生效的放在前面。

    loader作为预处理器通常会提供一些配置项,在引入loader的时候可以通过options将它们传入,有些loader可能使用query来代替options,从功能上来说它们并没有太大的区别,具体参考loader本身的文档。

    use:[
    	{
    		loader:'css-loader',
    		options:{}
    	}
    ]
    
  3. exclude和include:用来排除或包含指定目录下的模块,可接收正则表达式或字符串(文件绝对路径),以及由它们组成的数组。exclude和include同时存在时,exclude的优先级更高。该配置项通常是必加的,否则可能拖慢整体的打包速度。

    rules:[
    	{
    		test:/\.css$/,
    		use:['style-loader','css-loader'],
    		exclude:/src\/lib/,
    		include:/src/
    	}
    ]
    

    该规则仅对src目录生效,但是排除其中的src/lib目录。

  4. 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/
                }
    		}
    ]
    
  5. 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的配置文件中提取出来。

  1. 安装:npm install babel-loader @babel/core @babel/preset-env

    各个模块的作用如下:

    1. babel-loader:它是使Babel与Webpack协同工作的模块。
    2. @babel/core:它是Babel编译器的核心模块。
    3. @babel/preset-env:它是Babel官当推荐的预置器,可根据用户设置的目标环境自动添加所需的插件和补丁来编译ES6+代码。
  2. 在webpack.config.js中进行如下配置:

    rules:[
    	{
    		test:/\.js$/,
    		exclude:/node_modules/,
    		use:{
    			loader: 'babel-loader',
    			options: {
    				cacheDirectory: true,
    				presets: [[
    					'env', 
    					{modules: false}
    				]]
    			}
    		}
    	}
    ]
    
    1. 由于babel-loader通常属于对所有JS后缀文件设置的规则,所以需要在exclude中添加node_mudules,否则会令babel-loader编译其中所有的模块,这将严重拖慢打包的速度,并且有可能改变第三方模块的原有行为。
    1. 对于babel-loader本身添加了cacheDirectory配置项,它会启用缓存机制,在重复打包未改变过的模块时防止二次编译,同样也会加快打包的速度。cacheDirectory可以接受一个字符串类型的路径来作为缓存路径,这个值也可以为true,此时其缓存目录会指向node_modules/cache/babel-loader。
    2. 由于@babel/preset-env会将ES6 Module转化为CommonJS的形式,这会导致Webpack中的tree-shaking特性失效。将@babel/preset-env的modules配置项设置为false会禁用模块语句的转化,而将ES6 Module的语法交给Webpack本身处理。
ts-loader:

ts-loader是用于连接Webpack与Typescript的模块。

  1. 安装:npm install ts-loader typescript
  2. 在webpack.config.js进行如下配置:
    rules:[
    	{
    		test: /\.ts$/,
    		use: 'ts-loader'
    	}
    ]
    
  3. 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()插入页面中。

  1. 安装:npm install html-loader
  2. 在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文件加载后得到的是一个函数,可以接受一个变量对象并返回最终的字符串。

  1. 安装:npm install handlebars-loader handlebars
  2. 在webpack.config.js进行如下配置:
    rules:[
    	{
    		test: /\.handlebars$/,
    		use: 'handlebars-loader'
    	}
    ]
    
file-loader:

file-loader用于打包文件类型的资源,并返回其publicPath。

  1. 安装:npm install file-loader
  2. 在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形式编码。

  1. 安装命令:`npm install url-loader’。
  2. 在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>
  1. 安装命令:npm install vue-lodaer vue vue-template-compiler css-loader

    在安装时,除了必要的vue与vue-loader以外,还要安装vue-template-compiler来编译Vue模板,以及css-loader来处理样式(如果使用SCSS或LESS则仍需要对应的loader)。

  2. 在webpack.config.js进行如下配置:
    rules: [
    	{
    		test: /\.vue$/,
    		use: 'vue-loader'
    	}
    ]
    

自定义loader:

loader初始化:

将实现一个loader,它会为所有JS文件启用严格模式,也就是说,它会在文件头部加上如下代码:use strict

  1. 创建一个force-strict-loader文件夹,然后在该文件夹下执行npm初始化命令:npm init -y,会生成package.json文件。
    在这里插入图片描述
  2. 创建index.js,也就是loader的主体。
  3. 然后就可以在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进行控制。

  1. 修改loader;
    module.exports = function(content, sourceMap){	    
        if(this.cacheable){
            this.cacheable();
        }
        
        var useStrictPrefix = '\'use strict\';\n\n';
        return useStrictPrefix+content;
    };
    
获取options:

loader的配置项通过use.options传进来。

  1. 在webpack.config.js进行如下配置:为force-strict-loader传入一个配置项sourceMap;
    rules: [
    	{
    		test:  /\.js$/,
    		use:  {
    			loader: 'force-strict-loader',
    			options: {
    				sourceMap: true
    			}
    		}
    	}
    ]
    
  2. 接下来要在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;
     };
    
source-map:

source-map可以便于实际开发者在浏览器控制台查看源码。如果没有对source-map进行处理,最终也就无法生成正确的map文件,在浏览器的dev tool中可能就会看到错乱的源码。

  1. 修改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。

猜你喜欢

转载自blog.csdn.net/wsln_123456/article/details/106814874