上一章重学webpack系列(二) -- webpack解决的问题与实现模块化的具体实践,我们讲解了webpack解决了前端项目在实施落地的过程中遇到的一些问题,也初步去配置了webpack的entry、output、mode,实现了文件的打包,很明显在项目中我们不仅仅只有js文件,还有一些非js文件webpack也需要去做处理,那么处理非js文件的工具就是loaders了,那么这一章我们就来了解一下loaders究竟是何方神圣。
webpack怎么去加载非js文件
按照我们以往的经验,我们先尝试在文件目录中添加一个.css文件,在入口entry中配置css: "./src/css/index.css"
// src/css/index.css
.index {
display: flex;
justify-content: center;
align-items: center;
color: wheat;
background-color: red;
}
// webpack.config.json
entry: {
index: "./src/index.js",
add: "./src/add.js",
css: "./src/css/index.css"
},
我们打包的时候,控制台会出现报错:
出现这样的原因是因为,
css的代码语法与js的完全不同,webpack并不认识这样的语法,所以webpack提示应该使用loaders来解决这样的问题。那我们便可以去安装css-loader并在webpack.config.json里面配置css-loader来解决这个问题了。
css-loader
// 安装css-loader
npm i css-loader -S
// webpack.config.json
module.exports = {
...
module:{
rules:[
{
test:/\.css$/, // 用正则来匹配适用的文件
use:'css-loader' // 用loader来处理此文件
}
]
}
...
}
样式效果
在这里我们就可以解决打包报错的问题了,如果你想使用这个
index.css文件,你通过script标签引入到页面中,你会发现,此css文件的代码,并不能给节点加上样式。这是为什么呢?我们得去看一看打包过后的文件。在文件里我们可以看到这样的一段代码
var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));
// Module
___CSS_LOADER_EXPORT___.push([module.id, ".index {\n color: wheat;\n background-color: red;\n}", "",{"version":3,"sources":["webpack://./src/css/index.css"],"names":[],"mappings":"AAAA;EACE,YAAY;EACZ,qBAAqB;AACvB","sourcesContent":[".index {\n color: wheat;\n background-color: red;\n}"],"sourceRoot":""}]);
// Exports
const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
上面这段代码,出现了我们css中写的样式,表现为拆分了css结构成一个字符串,然后通过__WEBPACK_DEFAULT_EXPORT__暴露到当前的js文件中,换句话说就是css-loader只是加载css模块,但是并没有使用css模块。为了解决这个问题,我们还需要依赖另外一个loader。
style-loader
// 安装css-loader
npm i style-loader -S
// webpack.config.json
module.exports = {
...
module:{
rules:[
{
test:/\.css$/, // 用正则来匹配适用的文件
use:[
style-loader,
css-loader
]
// 用loader来处理此文件,如果是多个loader则必须要把style放在最前面,
// 因为style是使用,而其他的是加载、处理。
}
]
}
...
}
样式效果
扩展
为什么style-loader可以使用css样式呢?我们发现在style-loader打包之后,生成了这样的一串代码。
function insertStyleElement(options) {
// 创建style标签
var element = document.createElement("style");
// 设置标签以及标签属性
options.setAttributes(element, options.attributes);
// 插入到html中
options.insert(element, options.options);
return element;
}
原来style-loader的原理就是在页面创建了一个style标签去使用那些属性。
在js文件中引入css文件
如果我们单独打包css文件的话,那么我们就需要在使用层面,单独对js(源文件是css文件)文件去引入使用,我们也可以在js文件中使用import './css/index.css'来进行加载css文件到js文件中,实现统一打包,其实webpack也建议我们这样做。
// add.js
import './css/index.css'
// webpack.config.json
module.exports = {
mode: "development",
entry: {
add: "./src/add.js"
}
...
}
loader 特性
loader支持链式传递,一组链式的loader将按照相反的顺序执行。loader链中的第一个loader返回值给下一个loader。在最后一个loader,返回webpack所预期的JavaScript。loader可以是同步的,也可以是异步的。loader运行在Node.js中,并且能够执行任何可能的操作。loader接收查询参数。用于对loader传递配置。loader也能够使用options对象进行配置。- 除了使用
package.json常见的main属性,还可以将普通的 npm 模块导出为 loader,做法是在package.json里定义一个loader字段。 - 插件(
plugin)可以为loader带来更多特性。 loader能够产生额外的任意文件。
loader通过(loader)预处理函数,为JavaScript生态系统提供了更多能力。 用户现在可以更加灵活地引入细粒度逻辑,例如压缩、打包等。
常见loader的基础实践
-
babel-loader
- 作用:将Es6+ 语法转换为Es5语法。
- 配置
// 安装 npm i babel-loader @babel/core @babel/preset-env -S // webpack.config.json module.exports = { module: { rules: [ { test: /\.js$/, use: { loader: "babel-loader", options: { presets: [ ['@babel/preset-env', { targets: "defaults"}] ] } } }, ] } -
less-loader / scss-loader、style-loader、css-loader
- 作用:处理css/scss/less文件。
- 配置
// 安装 npm i less-loader / scss-loader css-loader style-loader -S // webpack.config.json module.exports = { module: { rules: [ { test: /\.less$/, // test: /\.scss$/, use: [ 'style-loader', 'css-loader', ’less-loader‘ // 'scss-loader' ] }, ] } -
file-loader
- 作用:用于处理文件类型资源,如
jpg,png等图片。返回值为publicPath为准。 - 配置
// 安装 npm i file-loader -S // webpack.config.json module: { rules: [ { test: /\.(png|jpg|jpeg)$/, use: [ { loader: "file-loader", options: { name: "[name]_[hash:8].[ext]", // .ext为文件扩展名eg: .png publicPath: "https://www.baidu.com" } } ] } ] } - 作用:用于处理文件类型资源,如
-
url-loader
- 作用:处理图片资源,根据大小选择打包方式。
- 配置
// 安装 npm i url-loader -S // webpack.config.json module: { rules: [ { test: /\.(png|jpg|jpeg)$/, use: [ { loader: "url-loader", options: { name: "[name]_[hash:8].[ext]", limit: 10240, // 这里单位为(b) 10240 => 10kb // 这里如果小于10kb则转换为base64打包进js文件,如果大于10kb则打包到dist目录 } } ] } ] } -
eslint-loader
- 作用:用于检查代码是否符合规范,是否存在语法错误,需要配合.eslintrc.js使用。
- 配置
// 安装 npm i npm i eslint-loader eslint -S // webpack.config.json module: { rules: [ { test: /\.ts$/, use: ["eslint-loader", "ts-loader"], enforce: "pre", exclude: /node_modules/ // 除此之外的文件夹 } ] } -
postcss-loader
- 作用:用于补充css样式各种浏览器内核前缀。
- 配置
// 安装 npm i postcss-loader -S // webpack.config.json module: { rules: [ { test: /\.scss$/, use: [ "style-loader", "css-loader", "sass-loader", "postcss-loader" ], include: /src/, }, ] } -
ts-loader
- 作用:编译ts文件,需要配合tsconfig.json使用。
- 配置
// 安装 npm i ts-loader -S // webpack.config.json module: { rules: [ { { test: /\.ts$/, use: "ts-loader", } } ] }
为什么webpack建议我们在js文件中引入其他资源
webpack不仅仅建议我们在js中引入css资源,只要是是当前项目依赖所有资源,webpack都建议我们在js文件中引入。为什么webpack要这样设计呢?
-
便于统一资源管理
- 如果我们单独去打包某个与
js产生依赖的css、图片、字体等,那么我们将会有额外的对html页面的操作。 - 如果这一部分功能废弃掉了,那么我们有必须要去操作
html页面,把不需要的模块资源移除掉,增加了额外操作。
- 如果我们单独去打包某个与
-
使逻辑更合理化
js代码肯定是依赖这些非js资源,才能实现整体的功能的。
-
防止必要文件丢失
- 通过
依赖关系,可以最大程度上保证资源文件不会丢失,在生产环境下可以避免重要经济损失。
- 通过
实现一个自己的loader
步骤
- 创建一个非
js文件。 - 引入到打包入口
js文件中。 - 创建一个自己命名的
loader。 - 在
webpack.config.json中配置自己的loader。
loader的基本要求
loader机制告诉我们在函数处理结果返回的时候,必须是一段JavaScript可执行的代码。
具体代码
// mkdown-loader.js
const {marked} = require('marked'); // 借用marked库,让md文件内容转化为html内容
module.exports = (moduleId) => {
const html = marked(moduleId)
// const htmlString = `module.exports = ${JSON.stringify(html)}`
// const htmlString = `export default ${JSON.stringify(html)}`
return html
}
// webpack.confih.json
module.exports = {
module:{
rules:[
{
test:/\.md$/,
use:[
'html-loader',
'./src/mkdown-loader.js'
]
}
]
},
}
使用html-webpack-plugin插件
我们先定义一个template模板当做插件模板。
<!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>Document</title>
</head>
<body>
<div class="index"></div>
</body>
</html>
配置插件之后的webpack.config.json
// webpack.confih.json
module.exports = {
module:{
rules:[
{
test:/\.md$/,
use:[
'html-loader',
'./src/mkdown-loader.js'
]
}
]
},
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
titie: 'title',
template: './index.html'
}),
],
}
之后在控制台就会输出一段html文本
我们再去看看打包后的代码的样子
获取loader的配置参数
如果我们在使用loader的时候,想给它传递一些配置项,比如
// webpack.config.json
module.exports = {
...
module: {
rules: [
{
test:/\.md$/,
use:[
{
loader: 'html-loader'
},
{
loader: './src/mkdown-loader.js',
options: {
name: '一溪之石',
age: 22,
sex: '男'
}
}
]
}
]
}
}
// mkdown-loader.js
module.exports = function(moduleId){
// 可以通过this.getOptions来获取options里面的参数
const options = this.getOptions();
const { name = 'null', age = 'null', sex='null' } = options;
const html = marked(moduleId);
console.log(html);
// .... 在loader里面处理其他逻辑
return html; // 返回给下一个loader处理
}
开发一个loader常用的Api
this.async:获取一个callback函数,处理异步。this.callback:同步loader中,返回的方法。this.emitFile:产生一个文件。this.getOptions:根据传入的schema获取对应参数,在webpack5中,schema已经不用再写了,getOptions可以获取到上下文中的配置参数。this.importModule:用于子编译器在构建时编译和执行请求。this.resourcePath:当前资源文件的路径。
总结
好了关于webpack的loader的机制,介绍到这里也就结束了,各位同学也看到了本文中使用到了html-webpack-plugin这个插件,插件确实能够让我们的项目啊,loader啊具有更丰富的功能,下一章我们就会探索一下webpack的插件机制,直通车 >>> 重学webpack系列(四) -- webpack的plugins机制的解读