为什么需要 webpack?
-
模块化:可以把复杂的程序细化为小的文件;
-
类似于TypeScript这种在JavaScript基础上拓展的开发语言:使我们能够实现目前版本的JavaScript不能直接使用的特性,并且之后还能转换为JavaScript文件使浏览器可以识别
-
Scss,less等CSS预处理器
模块化后网页中引入的静态资源多了以后有什么问题?
-
网页加载速度慢,因为我们要发起很多的二次请求;
-
要处理错综复杂的依赖关系。
浏览器不能直接识别TypeScript,scss,less等语法怎么办?
webpack 中的 loader 与 plugin
- Loader:用于对模块源码的转换,loader描述了webpack如何处理非javascript模块,并且在build中引入这些依赖。loader可以将文件从不同的语言(如TypeScript)转换为JavaScript。
- Plugin:目的在于解决loader无法实现的其他事,从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务。
Loader 的原理与实现
Loader 就像是一个翻译官,每个 loader 可以把源资源转换成新的结果输出并传递给下一个 loader ,但是最后一个 Loader 必须返回 JavaScript (浏览器只能运行js代码,不支持其他扩展语言)。
module:{
rules: [
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
}
- less-loader: 将 less 源代码转化为 css
- css-loader:处理 less-loader 输出的 css,找出 css 中依赖的资源(@import 等),压缩资源
- sytle-loader:处理 css-loader 输出的 css,把 css 转换成脚本加载的 js 代码插入到 DOM 中
- 那么如何实现一个loader呢?
1) 在config.js中配置项目中 .txt 结尾的文件使用我们的 demo-loader
// webpack.config.js
module:{
rules: [
{
test: /\.txt$/,
use: ['demo-loader'],
options: {
name: '梨花酱' // 将要变更的通过配置项传入
}
}
]
}
// test.txt
你好,我是蒋梨花
// app.js (入口文件引用)
const text = require(./text.txt)
console.log(test)
// demo-loader.js
const loaderUtils = require('loader-utils')
// 接收options配置
module.exports = function(source) {
const options = loaderUtils.getOptions(this)
source = source.replace(/蒋梨花/g, options.name)
return `module.exports = ${JSON.stringify(sorce)}`
// 最终需要返回一段可执行的js脚本
}
Plugin 的原理与实现
plugin是运行在webpak打包过程中的某段逻辑,它主要的作用是根据webpack提供的一些hooks来进行一些额外的操作,使 webpack 更加灵活扩展。
plugins: [
new HtmlWebpackPlugin()
]
compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。
compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
- 那么具体如何实现一个 plugin 呢?
// webpack.config.js
plugins: [
new MyTestPlugin({
msg: '你好我是梨花酱' // 传入的插件配置
})
]
// MyTestPlugin.js
const { ConcatSource } = require("webpack-sources") // 用来写入
class MyBannerPlugin {
constructor(options) {
// 获取传入的option信息
this.msg = options.msg
}, // 我们需要一个apply方法(为了获取compiler),接收compiler作为参数表示这次打包的上下文。
apply (compiler) {
const msg = this. msg // 指定挂载的 webpack 钩子函数
// 使用compiler钩子compilation,即编译(compilation)创建之后,执行插件。
compiler.hooks.compilation.tap("MyTestPlugin", compilation => {
// compilation的 optimizeChunkAssets 钩子,可以利用这个钩子实现为每个文件插入信息
compilation.hooks.optimizeChunkAssets.tap("MyTestPlugin", chunks => {
for (const chunk of chunks) {
for (const file of chunk.files) {
compilation.updateAsset(file, old => {
return new ConcatSource(msg,"\n", old);
});
}
}
})
})
}
}
module.exports = MyTestPlugin
-
首先需要声明一个 class 构造函数
-
在class里面定义一个apply方法,接收compiler作为参数表示这次打包的上下文。
-
指定挂载的webpack事件钩子
-
处理webpack内部实例的特定数据
-
功能完成后调用webpack提供的回调