零基础手把手教你学会Webpack(二)

1.前言

上篇文章讲了webpack入口,出口,以及一些最简单的应用,你可能会发现,单纯使用Webpack以及它的命令行工具来进行开发调试的效率并不高。以往只要编辑项目源文件(JS、CSS、HTML等),刷新页面即可看到效果。现在多了一步打包,我们在改完项目源码后要执行npm run build更新bundle.js,然后才能刷新页面生效。有没有更简便的方法呢?

2.webpack-dev-server

其实Webpack社区已经为我们提供了一个便捷的本地开发工具——webpack-dev-server。用以下命令进行安装:

 npm install webpack-dev-server -save -dev

安装指令中的--save-dev参数是将webpack-dev-server作为工程的devDependencies(开发环境依赖)记录在package.json中。这样做是因为webpack-dev-server仅仅在本地开发时才会用到,在生产环境中并不需要它,所以放在devDependencies中是比较恰当的。假如工程上线时要进行依赖安装,就可以通过npm install--production过滤掉devDependencies中的冗余模块,从而加快安装和发布的速度。安装完成之后可以修在package.json的script中添加dev指令

"scripts": {
 "build": "webpack",
 "dev": "webpack-dev-server"
}

然后可以指定下webpack-dev-server的发布路径,修改下webpack.config.js

const path = require('path');

module.exports = {
    context:path.resolve(__dirname, "./src"),
    entry: "./index.js",
    output: {
        path: path.resolve(__dirname, "E:/projectTest/out"),
        filename:"[name].js"
    },
    mode: "development",
    devServer:{
        publicPath:"/out/",
        port:8000,
        open: true,
        openPage: 'http://localhost:8000', // 浏览器默认打开的页面
    }
}

我们在配置中添加了一个devServer对象,它是专门用来放webpack-dev-server配置的。webpack-dev-server可以看作一个服务者,它的主要工作就是接收浏览器的请求,然后将资源返回。当服务启动时,会先让Webpack进行模块打包并将资源准备好(在示例中就是main.js)。当webpack-dev-server接收到浏览器的资源请求时,它会首先进行URL地址校验。如果该地址是资源服务地址(上面配置的publicPath),就会从Webpack的打包结果中寻找该资源并返回给浏览器。反之,如果请求地址不属于资源服务地址,则直接读取硬盘中的源文件并将其返回。

webpack-dev-server的特点

webpack-dev-server只是将打包结果放在内存中,并不会将打包文件放到项目中,在每次webpack-dev-server接收到请求时都只是将内存中的打包结果返回给浏览器。webpack-dev-server还有一项很便捷的特性就是live-reloading(自动刷新),当webpack-dev-server源文件进行了更新操作就会自动刷新浏览器,显示更新后的内容。该特性可以提升我们本地开发的效率。看下图
在这里插入图片描述

3.什么是loader?

loader 的字面意思是装载器,在 Webpack 中它的实际功能则更像是预处理器。 WebPack 本身只认识 JavaScript ,对于其他类型的资源必须预先定义一个或多个 loader 对其进行转译,输出为 Webpack 能够接收的形式再继续进行,因此 loader 做的实际上是一个预处理的工作。并且每个 loader 本质上都是一个函数。在 Webpack 4之前,函数的输人和输出都必须为字符串;在 Webpack 4之后, loader 也同时支持抽象语法树(AST)的传递,通过这种方法来减少重复的代码解析。用公式表达 loader 的本质则为以下形式:

 output =loader(input)

这里的 iput 可能是工程源文件的字符串,也可能是上一个 loader 转化后的结果,包括转化后的结果(也是字符串类型)、 source map ,以及 AST 对象; output 同样包含这几种信息,转化后的文件字符串、 source map ,以及 AST 。如果这是最后一个 loader 结果将直接被送到 Webpack 进行后续处理,否则将作为下一个 loader 的输人向后传递。举一个例子,当我们使用babel-loadey将ES6+的代码转化为 ES5 时,上面的公式如下:

ES5=babel-loader(ES6+)

loader可以是链式的。我们可以对一种资源设置多个loader,第一个 loader 的输人是文件源码,之后所有 loader 的输人都为上一个 loader 的输出。用公式表达则为以下形式:

output =loaderA(loaderB(loaderC(iput))));
//比如 less编译成css 
style样式=style-loader(css-loader(less-loader(less)))

下面来看下loader的源代码结构:

moudule.exports = function loader(content, map, meta) {
    var callback = this.async();
    var result = handler(content, map, meta);
    callback(
        null, //error
        result.content,//转换后的内容
        result.map,//转换后的source map
        result.meta//转换后的AST
    )
}

从上面代码可以看出, loader 本身就是一个函数,在该函数中对接收到的内容进行转换,然后返回转换后的结果(可能包含 source map 和 AST 对象)。

4.引入loader

Webpack 无法处理 CSS 语法,需要使用一个合适的 loader 来处理这种文件。下面我们把css—loader加到工程中。 loader 都是一些第三方 npm 模块, Webpack 本身并不包含任何 loader ,因此使用 loader 的第一步就是先从 npm 安装它。在工程目录执行命令: npm install css-loader接下来我们将 loader 引人工程中,具体配置如下:

npm install css-loader
   module: {
        rules: [{
            test: /\.css$/,
            exclude: /node_module/,
            use: [
                "css-loader"
            ]
        }]
    },

相关的配置都在 module 对象中,其中module.rules代表了模块的处理规则,每条规则内部可以包含很多配置项,这里我们只使用了最重要的几项test和use,exclude,include!

  1. test:可接收一个正则表达式或者一个元素为正则表达式的数组,只有正则匹配上的模块才会使用这条规则。在本例中以/\.css$/来匹配所有以.css结尾的文件。
  2. use: 可接收一个数组,数组包含该规则所使用的 loader 。在本例中只配置了一个css-loader,在只有一个 loader 时也可以简化为字符串“css-loader”。
  3. exclude 与 include 是用来排除或包含指定目录下的模块,可接收正则表达式或者字符串(文件绝对路径),以及由它们组成的数组,上面 exclude 的含义是,所有被正则匹配到的模块都排除在该规则之外,也就是谭 node modules 中的模块不会执行这条规则。该配置项通常是必加的,否则可能拖慢整体的打包速度。

5.常用loader

使用 webpack 的过程中经常会遇到以下这样的问题,对于 XX 资源应该使用哪个 loader ?,要实现 XX 功能应该使用哪个 loader ?因为每时每刻都可能有新的 loader 发布到 npm 上(这也是 Webpack 社区强大的体现)。希望通过以下介绍,能了解这些 loader 的用法,更重要的是了解 loader 能做什么,这样在遇到问题时首先可以判断出能否使用 loader 来解决,进而寻找相应的 loader 或采取其他方案。

5.1.babel-loader

babel-loader用来处理ES6+并将其编译为ES5,它使我们能够在工程中使用最新的语言特性(甚至还在提案中),同时不必特别关注这些特性在不同平台的兼容问题。在安装时推荐使用以下命令:

npm install babel-loader @babel/core @babel/preset-env
  1. babel-loader:它是使 Babel 与 Webpack 协同工作的模块 。
  2. @babel/core :顾名思义,它是 Babel 编译器的核心模块。
  3. @babel/preset-env:它是 Babel 官方推荐的预置器,可根据用户设置的目标环境自添加所需的插件和补丁来编译ES6+代码。
 module: {
        rules: [{
                test: /\.css$/,
                exclude: /node_module/,
                use: [
                    "css-loader"
                ]
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader",
                    options: {
                        cacheDirectory: true,
                        presets: [
                            ["@babel/preset-env", {
                                modules: false
                            }]
                        ]
                    }
                },

            }
        ]
    }

我们添了 caceDirectory 配置项,它会启用缓存机制,在重复包未改变过的模块时防止第二次编译,同样也会加快打包的速度,缓存目录在node_module/.cache/babel-loader
在这里插入图片描述
由于@babel/preset-env会将ES6的Module转化为commonJs的形式,这会导致webpack中的tree-shaking失效,所以将module配置项设置为false会禁用模块化语句的转化,将ES6 Module的语法交给webpack处理

5.1.1. tree-shaking

ES6 Module 依赖关系的构建是在代码编译时而非运行时。基于这项特性 Webpack 提供了 tree shaking 功能,它可以在打包过程中帮助我们检测工程中没有被引用过的模块,这部分代码将永远无法被执行到,因此也被称为“死代码”。 Webpack 会对这部分代码进行标记,并在资源压缩时将它们从最终的 bundle 中去掉。

//index.js
import {test} from"./b"
test();

//b.js
export function test(){
    console.log("test");
}

export function fn(){
    console.log("fn");//  没有其他模块被引用,就是上面说的"死代码"
}

tree shaking 只能对ES6 Module 生效。有时我们会发现只引用了某个库中的一个接口,却把整个库加载进来了,而 bundle 的体积并没有因为 tree shaking 而减小。这可能是由于该库是使用 CommonJS 的形式导出的,为了获得更好的兼容性,目前大部分的 npm 包还在使用 CommonJS 的形式。也有一些 npm 包同时提供了ES6 Module 和 CommonJS 两种形式导出,我们应该尽可能使用ES6 Module 形式的模块,这样 treeshaking 的效率更高

//index.js
//es6语法 let  箭头函数
let promiseA = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("index.js");
    }, 1000)
})
promiseA.then((data) => {
    console.log(data);
})

我们把index.js改成es6的写法,在执行下npm run build 看下转换的代码:
在这里插入图片描述

5.2. ts-loader

ts-loader 和babel-loader的性质类似,它是用于连接webpack与typescript模块,下面来安装typescript和ts-loader

npm install ts-loader typescript -save -dev

webpack.config.js配置:

 module: {
        rules: [{
                test: /\.css$/,
                exclude: /node_module/,
                use: [
                    "css-loader"
                ]
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader",
                    options: {
                        cacheDirectory: true,
                        presets: [
                            ["@babel/preset-env", {
                                modules: false
                            }]
                        ]
                    }
                }
            },
            {
                test: /\.ts$/,
                exclude: /node_modules/,
                use: "ts-loader"
            }
        ]
    },
    // 添加.ts 和 .js 作为可解析的扩展名。
    resolve: {
        extensions: ['.ts', '.js']
    }

需要注意的是, Typescript 本身的配置并不在ts-loader中,而是必须要放在工程目tsconfig.json

{
    "compilerOptions": {
        "target": "es5",// 目标语言的版本
        "sourceMap": true, //生成目标文件的sourceMap文件
        "module": "CommonJS", // 生成代码的模板标准
    }
}

compilerOptions为配置编译选项,下面是所有的参数含义

"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息 
"target": "ES5", // 目标语言的版本
"module": "CommonJS", // 生成代码的模板标准
                 // 默认值 target === "es3" or "es5" ?"commonjs" : "es6"
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,
                      // 即开启时应设置"module": "AMD",
"lib": [], // 编译时引入的 ES 功能库,包括:es5 、es6、es7、dom 等。// 如果未设置,则默认为: target 为 es5 时: ["dom", "es5", "scripthost"] 
//target 为 es6 时: ["dom", "es6", "dom.iterable", "scripthost"]
"allowJS": true, // 允许编译器编译JS,JSX文件
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定输出目录
"rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
"declaration": true, // 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./file", // 指定生成声明文件存放目录
"emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
"sourceMap": true, // 生成目标文件的sourceMap文件
"inlineSourceMap": true, // 生成目标文件的inline SourceMap,//inline SourceMap会包含在生成的js文件中
"declarationMap": true, // 为声明文件生成sourceMap
"typeRoots": [], // 声明文件目录,默认时node_modules/@types
"types": [], // 加载的声明文件包
              //如果指定了某个值, 她会在 typeRoots 下找这个包,找到了就只加载这个包
"removeComments":true, // 删除注释 
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true, // 发送错误时不输出任何文件
"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true, // 开启所有严格的类型检查
"alwaysStrict": true, // 在代码中注入'use strict'
"noImplicitAny": true, // 不允许隐式的any类型
"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
"strictFunctionTypes": true, // 不允许函数参数双向协变
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"strictBindCallApply": true, // 严格的bind/call/apply检查
"noImplicitThis": true, // 不允许this有隐式的any类型
"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true, //每个分支都会有返回值
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { // 路径映射,相对于baseUrl
    // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
   "jquery": ["node_modules/jquery/dist/jquery.min.js"]
  },
"rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,
                          //即编译后引入文件的位置可能发生变化,
                          //这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true, // 打印输出文件
"listFiles": true , // 打印编译的文件(包括引用的声明文件)
"jsx":"Preserve"   //在 .tsx 中支持 JSX :React 或 Preserve
"jsxFactory":""   //默认值 React.createElement	。  jsx 设置为 React 时使用的创建函数

添加test.ts文件

//test.ts
export class Person {
    public age: number;
    constructor(age: number):void {
        this.age = age;
    }
    public getAge(): number {
        return this.age
    }
}
//index.js
import {Person} from "./test"

let person =new Person(18);
console.log(person.getAge());//18

看输出结果:
在这里插入图片描述

再看下ts解析后的结果:
在这里插入图片描述

5.3. less-loader

Less同样是对css的一种扩展,它也需要安装less-loader和其本身的编译模块!

npm install less-loader less -save -dev

webpack.config.js配置:

const path = require('path');

module.exports = {
    context: path.join(__dirname, "./src"),
    entry: "./index.js",
    output: {
        path: path.resolve(__dirname, "./out"),
        filename: "[name].js",
    },
    mode: "development",
    devServer: {
        publicPath: "/out/",
        port: 8000,
        open: true
    },
    module: {
        rules: [{
                test: /\.less$/,
                use: ["style-loader",
                    {
                        loader: "css-loader",
                        options: {
                            sourceMap: true
                        }
                    }, {
                        loader: "less-loader",
                        options: {
                            sourceMap: true
                        }
                    }
                ],
                exclude: /node_modules/
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader",
                    options: {
                        cacheDirectory: true,
                        sourceMap:true,
                        presets: [
                            ["@babel/preset-env", {
                                modules: false
                            }]
                        ]
                    }
                }
            },
            {
                test: /\.ts$/,
                exclude: /node_modules/,
                use: "ts-loader"
            }
        ]
    },
    resolve: {
        extensions: ['.ts', '.js']
    }
}

修改下index.html内容

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My first Webpack app</title>
</head>

<body>
    <script src="./out/main.js"> </script>
</body>
<p>
    <span>loader-less</span>
</p>

</html>

添加index.less文件

p {
    span {
        color: red;
    }
}

执行下npm run build,因为加了sourceMap,直接看less解析之后的文件:
在这里插入图片描述

5.4. file-loader

file-loder用于打包文件类型的资源,并返回其publicPath,那么首先肯定要先安装它

npm install file-loader -save -dev

webpack.config.js配置:

const path = require('path');

module.exports = {
    context: path.join(__dirname, "./src"),
    entry: "./index.js",
    output: {
        path: path.resolve(__dirname, "./out"),
        filename: "[name].js",
    },
    mode: "development",
    devServer: {
        publicPath: "/out/",
        port: 8000,
        open: true
    },
    module: {
        rules: [{
                test: /\.less$/,
                use: ["style-loader",
                    {
                        loader: "css-loader",
                        options: {
                            sourceMap: true
                        }
                    }, {
                        loader: "less-loader",
                        options: {
                            sourceMap: true
                        }
                    }
                ],
                exclude: /node_modules/
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader",
                    options: {
                        cacheDirectory: true,
                        sourceMap: true,
                        presets: [
                            ["@babel/preset-env", {
                                modules: false
                            }]
                        ]
                    }
                }
            },
            {
                test: /\.ts$/,
                exclude: /node_modules/,
                use: "ts-loader"
            },
            {
                test: /\.(png|jpg|gif)$/,
                use: {
                    loader: "file-loader",
                    options: {
                        name: "[name]_[hash].[ext]", //文件名
                        outputPath:"images" //打包生成的文件存放的路径
                    }
                }

            }
        ]
    },
    resolve: {
        extensions: ['.ts', '.js']
    }
}

先看下目录结构:
在这里插入图片描述

修改index.less文件

p {
    span {
        color: red;
    }
}
.bg {
    width: 50px;
    height: 50px;
    background: url("./1.jpg") no-repeat center center;
    background-size: cover;
}

在index.html加个class为bg的div

<body>
    <p>
        <span>less-loader</span>
        <div class="bg"></div>
    </p>
</body>

执行npm run build
在这里插入图片描述
打包完成之后多了个images文件,文件夹下面有这张背景图1.jpg,那么要是我们在js里面引入这个文件,那么,输出的是什么呢,看下面代码

import img from"./1.jpg"
console.log(img); 

在这里插入图片描述
是文件的请求路径,也是less文件的路径(webpack已经帮从"./1.jpg"/你改成上图的路径)
在这里插入图片描述
我们同样可以修改文件的请求路径改成相对路径,那么就要加上publicPath配置:

{
    test: /\.(png|jpg|gif)$/,
    use: {
        loader: "file-loader",
        options: {
            name: "[name]_[hash].[ext]",
            outputPath:"images",
            publicPath:"./out/images"
        }
    }

}

再执行下 npm run build 看下图:
在这里插入图片描述

在这里插入图片描述


5.5. url-loader

url-loader和file-loader类似,唯一不用的是用户可以设置一个数值(文件的大小),如果文件小于这个数值就用file-loader解析,如果小于这个数值就返回文件的base64的形式编码,给图片配了 url-loader 在配置里面就不要再给图片配 file-loader 了 ,因为 url-loader 默认会使用 file-loader 来处理图片的路径关系的,只是加了个当图片太大把路径转成了base64的功能。简单的来说url-loader封装了file-loader。url-loader不依赖于file-loader,即使用url-loader时,只需要安装url-loader即可,不需要安装file-loader,因为url-loader内置了file-loader。

npm install url-loader -save -dev

修改webpack.config.js配置:

const path = require('path');

module.exports = {
    context: path.join(__dirname, "./src"),
    entry: "./index.js",
    output: {
        path: path.resolve(__dirname, "./out"),
        filename: "[name].js",
    },
    mode: "development",
    devServer: {
        publicPath: "/out/",
        port: 8000,
        open: true
    },
    module: {
        rules: [{
                test: /\.less$/,
                use: ["style-loader",
                    {
                        loader: "css-loader",
                        options: {
                            sourceMap: true
                        }
                    }, {
                        loader: "less-loader",
                        options: {
                            sourceMap: true
                        }
                    }
                ],
                exclude: /node_modules/
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader",
                    options: {
                        cacheDirectory: true,
                        sourceMap: true,
                        presets: [
                            ["@babel/preset-env", {
                                modules: false
                            }]
                        ]
                    }
                }
            },
            {
                test: /\.ts$/,
                exclude: /node_modules/,
                use: "ts-loader"
            },
            {
                test: /\.(png|jpg|gif)$/,
                use: {
                    loader: "url-loader",
                    options: {
                        limit:1024000, //这里的单位是B,不是KB
                        name: "[name]_[hash].[ext]",
                        outputPath:"images",
                        publicPath:"./out/images"
                    }
                }

            }
        ]
    },
    resolve: {
        extensions: ['.ts', '.js']
    }
}

执行下npm run build,先看下目录结构:
在这里插入图片描述
已经没有images文件夹了,因为转成了base 64编码了,在看下输入的文件名和引入的地址,看下图:
在这里插入图片描述
在这里插入图片描述

6.总结

loader就像webpack的翻译官,webpack只能接受javascript,为了能够处理其他的类型资源,就必须使用loader将资源编译成webpack认识的形式,在配置 loader 时,实际上定义的是模块规则(rules),对哪些模块生效(exclude,test,include),使用哪些loader(use), loader可以是链式的,并且每一个都允许拥有自己的配置项。loader 本质上是一个函数。第一个 loader 的输入是源文件,之后所有 loader 的输入都是上一个 loader 的输出,最后一个 loader 则直接输出给Wiebpack.


这篇写的又有点长了!下篇接着写!后续持续更新!想和我一起学webpack的朋友,或者觉得写的还可以的,加个关注!交个朋友呗!

  • 46
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 101
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 101
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值