马轩

个人主页

欢迎来到我的个人站~


webpack

webpack

其实很久之前就像要去系统学习一下这个东西了,只是一直都是忙着处理各种乱七八糟的事情,一直没有整块的事件,正好呢,最近想要去做这个格式化网站的事情,中间有一点自己的事件,就刚好来看一下这个东西吧。相对来说更加系统一点吧。

part 1 webpack 简介

webpack 就是一个打包工具,将我们所写的js代码和css等等其他代码搞到一起来。安装webpack

npm init
npm i webpack webpack-cli 
#这里面的webpack是核心模块,webpack-cli是命令行工具

#执行打包命令哈:
webpack --entry=./index.js --output-filename=bundle.js --mode=development
# entry 指定了入口文件  output-filename指定了出口文件,输出文件都会放在dist文件夹下面。 mode指定了是开发模式。

#使用 script 标签,在package.json 文件下面的script中设置一些我们要使用的命令,比如说我们可以把打包命令写在其中
"script":{
	"build":"webpack --entry=./index.js --output-filename=bundle.js --mode=development"
}

# 这样的话,我们下次就不用再去执行那么一长串的命令了,只需要去执行一个
npm run build 就可以了

但是 这种方式也是具有问题的,因为这样的话,实际上我们所有的内容都是要放在 那个命令行里面的,如果遇到更加复杂的情况的话就会写更加麻烦的字符串。

因此我们引入了webpack.config.js。我们在系统的根目录下面创建了 webpack.config.js

module.exports = {
	entry:"./src/index.js",
	output:{
		filename:"bundle.js"
	},
	mode:"development"
}

这样的话我们在package.json中就不用那么去写了,就可以直接去配置为

"script":{
	"build":"webpack"
}

我们就不用在后面写一大堆非常复杂的配置选项了,因为绝大多数的内容都在 webpack.config.js 中配置好了。

最后介绍一个非常好用的工具 webpack-dev-server

npm i webpack-dev-server --save-dev

在webpack.config.js中配置

"script":{
	"dev":"webpack-dev-server"
}

这样我们就可以使用 npm run dev 来本地启动这个网页了,然后最重要的是他能根据我们的更改内容动态打包。这个功能和nodemon基本一样。

part2 模块打包

  1. CommonJS 和 ES6 module 最本质的区别在于前者对模块的依赖的解决是“动态的,后者则是”静态的“。动态是指模块依赖关系建立在代码运行的时候,而不是代码编译的时候。

CommonJS中由于是动态引入,所以可以通过 if 语句来判断,是否引入某一个模块。

ES6 module中的引入、导入语句都是生命式的,它不支持导入的路径是一个表达式,并且导入、导出语句必须位于模块的顶层作用域。因此说ES6是一个静态模块解构。

  1. 值拷贝和动态映射

CommonJS 是对值的 copy 这个操作不会对原来的产生任何影响,但是 ES6 module 只是引用原来的内容,会实时更新的。

  1. 循环引用的问题

CommonJS 中对于循环引用的问题,都是再次引用的时候当作一个空对象。

ES6 module 中都是记录为一个引用,实时的获取,也就是如果已经放入到引用队列中了,那么就不会再进入到该文件中执行了,直接返回对它的引用。

part3 资源输入输出

  1. webpack 对资源处理流程: 在一切的流程的最开始的时候,我们需要指定一个或多个入口,也就告诉webpack要从源码的哪个目录下面开始进行打包。这些依赖关系的模块会在打包的时候被封装为一个chunk。

一个 entry 对应着一个 chunk,一个 chunk 对应着一个 bundle bundle为一个打包之后的代码文件。

多个入口,就会对应着多个chunk,同时对应着多个 bundle 输出文件。

  1. 资源入口的相关配置

    entry:

    1. 直接传入入口文件
    module.exports = {
    	entry:"./src/index.js",
    	output:{
    		filename:"bundle.js"
    	},
    	mode:"development"
    }
    
    1. 输入类型入口
    module.exports = {
    	entry:["babel-polyfill" , "./src/index.js"]
    }
       
    // 上述的配置等同于
       
    module.export = {
    	entry:"./src/index.js"
    }
    //index.js
    import "babel-polyfill"
    
    1. 对象类型入口

    如果想要使用多个入口,那么就需要使用键值对的形式来进行

    module.exports = {
    	index:"./src/index.js",
    	lib:"./src/lib.js"
    }
    

    这样就有了两个入口文件了,他们分别为 index.js 和 lib 他们对应着两个 chunk 然后这两个 chunk 还会打包成为两个 bundle.

    1. 函数类型入口,这个函数返回的内容跟之前的内容一样就可以了.
    2. 提取 vendor

假如工程越来越大,只产生一个bundle文件,并且他的体积很大,一旦产生了代码更新,即便是只有一点改动,用户都要去下载整个资源文件,这对于页面的性能是非常不友好的.

为了解决这个问题,我们可以使用提取vendor的方法.在webpack中,vendor一般是工程中所要使用的库,框架等等第三方模块集中打包而产生的 bundle.

module.exports = {
    app1:"./src/index1.js",
    app2:"./src/index2.js",
    vendor:["axios"]
}
  1. 配置资源出口

在资源配置的出口处,所有和出口相关的配置都集中在output对象中. 其中可以包含很多属性,但是我们主要介绍几个经常被使用的属性.

​ 1.filename

module.exports = {
	entry: "./src/app.js",
	output:{
		filename : "bundle.js"
	}
}

· 2.path

path可以指定资源输出的路径,要求值必须为绝对路径,这个时候就要借助 path 来实现。

const path = require("path")
module.exports = {
	entry:"./src/app.js",
	output:{
		filename:"bund.js",
		path:path.join(__dirname,"dist")
	}
}

这样就可以将代码输出到 dist文件夹下面了。

part4 loader

到目前位置,我们所接触到的打包都是js文件,对于其他文件实际上我们是无法打包的。问题如下:

You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> html{
|     background-color: #fff;
| }
 @ ./src/addcontent.js 1:0-20

​ 对于一个web项目来说是包含有各种各样的静态资源的,包含HTML、CSS、模板、图片、字体等等,这些东西,都可以通过一定的方式,像我们之前加载js文件一样被加载打包。loader本质上就是一个函数,在该函数中对接函数接收到的内容进行转化,然后返回转换后的结果。下面是解决css引入问题的方案:

//shell
npm i style-loader css-loader

// webpack.config.js
module.exports = {
	module:{
		rules:[
			{
				test:/\.css/,
				use:["style-loader","css-loader"]
			}
		]
	}
}

同时我们可以给每一个 loader 配置 options,

module.exports = {
	module:{
		rules:[
			{
				test:/\.css$/,
				use:[
					"style-loader",
					{
						loader:"css-loader",
						options:{
							// 这个地方具体要去查看,该loader的文档。
						}
					}
				]
			}
		]
	}
}

在规则这选项里面,有一系列的对象,每一个对象中包含的是一个规则。这个规则里面

  1. test 通过一个正则表达式来确定将该规则应用给哪个文件。然后使用 use 字段来确定使用哪个、哪些loader,
  2. 然后 从后到前依次使用 loader,因为他们可以上一个loader的结果返回给下一个loader,就好比上面的情况。将一个css文件使用css-loader就是将css引入到js代码中去,然后使用style-loader将样式包装成style标签放在页面中。
  3. 可以包含有exclude 和 include 可以接受正则表达式或者字符串,但是这里面 exclude 的有限级高于 include。
  4. resource 和 issuer 这两个配置。在webpack中,我们认为 resource 是被引入的文件,issuer是引入文件,
// 下面是都上面的内容的简要描述
rules[
	{
		test:/\.css$/,
		use:["style-loader","css-loader"],
		exclude:/node_modules/,
// 这个地方 exclude 排除node_modules 其实就是排除通过npm引入的包,因为引入他们会加大负担。
		include:/node_modules\/awesome-ui/,
// 这个地方 include 不是起作用,因为 exclude 的优先级高于 include
        resource:{
        	test:/\.css$/,
            exclude: /node_modules/,
    	},
        issuer:{
            test:/\.js$/,
            exclude:/node_modules/
        }
	}
]

4.1 常见的loader

下面来介绍一些常见的 loader,

1.babel-loader

babel是非常常用的一个工具哈,它可以将es6+的语法翻译为es5的语法,能够让我们在工程中使用更新的js语法,同还不用去考虑平台的兼容性问题。

# 首先去安装Babel 所需要的一些包 包含有 babel-loader @babel/core @babel/preset-env
npm install babel-loader @babel/core @babel/env

rules:[
	{
		test:/\.js$/,
		exclude:/node_modules/,
		use:{
			loader:"babel-loader",
			options:{
				cacheDirectory:"true",
				presets:[
					"@babel/preset-env",
					{
						module : false
					}
				]
			}
		}
	}
]
2.ts-loader

ts-loader的性质和 babel-loader具有一些相似性,他是用于连接webpack 和 TypeScript 的模块

npm install ts-loader typescript

//webpack.config.js
rules:{
	test:/\.ts$/,
	use:["ts-loader"]
}

//同时 ts 的配置是放在 tsconfig.json 中的
{
	complierOptions:{
		"target":"es5",
		"sourceMap":true
	}
}
3.file-loader

file-loader 可以对文件进行打包,然后返回其 publicPath。

npm install file-loader

// 在 webpack.config.js 中配置如下
const path = require("path")

module.exports = {
	entry:"./app.js",
	output:{
		filename:'[name].js',
        path:path.join(__dirname,"dist"),
        publicPath:"/dist
	},
	use:{
        test:/\.(png|gif|jpg)$/,
        use:[{
            loader : "my-file-loader",
            options:{
                module:false
            }
        }],
    }
}
4.url-loader

他和file-loader功能基本一样。

part5. 自己实现loader

5.1 关于loader

  1. loader 本质上就是一个到处内容为函数的模块
  2. loader 默认就是可以接受上游传递过来的资源文件或者结果
  3. compiler 会拿到最后一个 loader 的产出结果,这个结果为一个 string 或者 buffer

5.2 loader的分类

  1. 普通 loader : 没有任何的配置
  2. 前置 loader : enforce 属性配置 pre
  3. 后置 loader : enforce 属性配置 post
  4. 行内 loader : 使用 !进行分割

不写 enforce 的话,就是从右向左进行,然后我们可以使用 enforce 进行。

运行顺序是 pre > normal > inline > post

5.3 配置符号

为了使用方便,可以通过一些符号来设置某些的开启和关闭

! 表达的意思是跳过normal -! 表达的意思是去掉 pre 和 mormal !! 表示去掉 pre normal post

5.4 loader的组成

一个完整的 loader 是由 normal loader pitchLoader 组成的

image-20211001162544322

当pitch有返回值的时候,那么就会直接跳过之后的loader和自己的normal loader 直接去执行到后面的normal loader,就比如说上面的图中2-pitch返回了一个值,那么我们直接跳到 3-normal 中去。

5.5 loader 获取配置参数

在使用 loader 的时候,用户可以给定一些参数,在 loader 的实现过程中可以拿到,可以服务于 loader 的业务逻辑。

// 我们可以通过 getOptions 来进行获取参数
const {getOptions} = require("loader-utils")

function loader(content){
	const options = getOptions(this)
	console.log(options)
	return content + "// test-loader"
}
module.exports = loader

5.6 一般loader展示

const {getOptions,interpolateName} = require("loader-utils")
function loader(content){
    const options = getOptions(this) || {}
    let filename = interpolateName(this,options.filename,{content:content})
    console.log(filename,"<===")

    // 将文件 copy 至指定的目录。
    this.emitFile(filename,content)

    // 最终返回一个 buffer 或者字符串直接给 compiler 使用。
    return `module.exports=${JSON.stringify(filename)}`
}

loader.raw = true

module.exports = loader


//另外一个简单的

function loader(content){
    console.log("normal-loader is running!")
    return content
}
module.exports = loader

loader 就是导出一个函数,然后函数中可以返回一个js可以执行的字符串或buffer。比如这个函数就是返回了一个 字符串。

part6 样式处理

之前我们使用 css-loader 和 style-loader 解决了 引入样式的问题,但是这还有一个问题就是,css和js之间的隔离问题,他没有输出单独的 css 文件,我们可以使用单独的插件来进行,mini-css-extract-plugin 和 extract-text-webpack-plugin.

下面来掩饰如何使用这样的插件:

npm i extract-text-webpack-plugin


const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin")
module.export = {
	entry:"./app.js",
	output:{
		filename:'[name].js',
        path:path.join(__dirname,"dist"),
        publicPath:"/dist
	},
	module:{
		rules:[
			{
				test:/\.css$/,
                use:[{
                    loader : ExtractTextWebpackPlugin.extract({
                        fallback:"style-loader",
                        use:"css-loader"
                    }),
                    options:{
                        module:false
                    }
                }],
			},
		]
        
    },
    plugins:[
    	new ExtractTextWebpackPlugin("bundle.js") 
    ]
}
// 这样提取 css 可以单独使用 ExtractTextWebpackPlugin 进行,然后对于使用中,我先去使用 use 中的 css-loader 进行。然后在plugins中引入该插件的实例,然后实例中传入css文件名
// 如果是多个bundle中的话,那么就可以使用 
 new ExtractTextWebpackPlugin("[name].css")

对于Less Sass 他们和babel 类似,都是只是提供一个接口,但是真正的转换是要通过别的进行的。

npm install sass-loader node-sass
npm install less-loader node-less

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦