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 模块打包
- CommonJS 和 ES6 module 最本质的区别在于前者对模块的依赖的解决是“动态的,后者则是”静态的“。动态是指模块依赖关系建立在代码运行的时候,而不是代码编译的时候。
CommonJS中由于是动态引入,所以可以通过 if 语句来判断,是否引入某一个模块。
ES6 module中的引入、导入语句都是生命式的,它不支持导入的路径是一个表达式,并且导入、导出语句必须位于模块的顶层作用域。因此说ES6是一个静态模块解构。
- 值拷贝和动态映射
CommonJS 是对值的 copy 这个操作不会对原来的产生任何影响,但是 ES6 module 只是引用原来的内容,会实时更新的。
- 循环引用的问题
CommonJS 中对于循环引用的问题,都是再次引用的时候当作一个空对象。
ES6 module 中都是记录为一个引用,实时的获取,也就是如果已经放入到引用队列中了,那么就不会再进入到该文件中执行了,直接返回对它的引用。
part3 资源输入输出
- webpack 对资源处理流程: 在一切的流程的最开始的时候,我们需要指定一个或多个入口,也就告诉webpack要从源码的哪个目录下面开始进行打包。这些依赖关系的模块会在打包的时候被封装为一个chunk。
一个 entry 对应着一个 chunk,一个 chunk 对应着一个 bundle bundle为一个打包之后的代码文件。
多个入口,就会对应着多个chunk,同时对应着多个 bundle 输出文件。
-
资源入口的相关配置
entry:
- 直接传入入口文件
module.exports = { entry:"./src/index.js", output:{ filename:"bundle.js" }, mode:"development" }
- 输入类型入口
module.exports = { entry:["babel-polyfill" , "./src/index.js"] } // 上述的配置等同于 module.export = { entry:"./src/index.js" } //index.js import "babel-polyfill"
- 对象类型入口
如果想要使用多个入口,那么就需要使用键值对的形式来进行
module.exports = { index:"./src/index.js", lib:"./src/lib.js" }
这样就有了两个入口文件了,他们分别为 index.js 和 lib 他们对应着两个 chunk 然后这两个 chunk 还会打包成为两个 bundle.
- 函数类型入口,这个函数返回的内容跟之前的内容一样就可以了.
- 提取 vendor
假如工程越来越大,只产生一个bundle文件,并且他的体积很大,一旦产生了代码更新,即便是只有一点改动,用户都要去下载整个资源文件,这对于页面的性能是非常不友好的.
为了解决这个问题,我们可以使用提取vendor的方法.在webpack中,vendor一般是工程中所要使用的库,框架等等第三方模块集中打包而产生的 bundle.
module.exports = {
app1:"./src/index1.js",
app2:"./src/index2.js",
vendor:["axios"]
}
- 配置资源出口
在资源配置的出口处,所有和出口相关的配置都集中在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的文档。
}
}
]
}
]
}
}
在规则这选项里面,有一系列的对象,每一个对象中包含的是一个规则。这个规则里面
- test 通过一个正则表达式来确定将该规则应用给哪个文件。然后使用 use 字段来确定使用哪个、哪些loader,
- 然后 从后到前依次使用 loader,因为他们可以上一个loader的结果返回给下一个loader,就好比上面的情况。将一个css文件使用css-loader就是将css引入到js代码中去,然后使用style-loader将样式包装成style标签放在页面中。
- 可以包含有exclude 和 include 可以接受正则表达式或者字符串,但是这里面 exclude 的有限级高于 include。
- 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
- loader 本质上就是一个到处内容为函数的模块
- loader 默认就是可以接受上游传递过来的资源文件或者结果
- compiler 会拿到最后一个 loader 的产出结果,这个结果为一个 string 或者 buffer
5.2 loader的分类
- 普通 loader : 没有任何的配置
- 前置 loader : enforce 属性配置 pre
- 后置 loader : enforce 属性配置 post
- 行内 loader : 使用 !进行分割
不写 enforce 的话,就是从右向左进行,然后我们可以使用 enforce 进行。
运行顺序是 pre > normal > inline > post
5.3 配置符号
为了使用方便,可以通过一些符号来设置某些的开启和关闭
! 表达的意思是跳过normal -! 表达的意思是去掉 pre 和 mormal !! 表示去掉 pre normal post
5.4 loader的组成
一个完整的 loader 是由 normal loader pitchLoader 组成的
当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