webpack5 初始化《JJ》主题 下部
图片处理
yarn add url-loader file-loader --dev
很多人教程都莫名其妙,一张嘴就说推荐使用url-loader解析器,但是闭嘴不提url-loader是基于file-loader的,所以用url就必须两个都装,否则一旦你设置了url的limit限制文件大小,文件超出设置得大小后,就会报file-loader不存在的错误。
webpack.config.js
module.exports = (env, argv) => ({
//模块-解析器loader
module: {
rules: [
//图片文件
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: "url-loader",
options: {
name: "[name].[hash:16].[ext]", // 文件名.hash.文件扩展名 默认格式为[hash].[ext]
limit: 10, // 小于10KB图片,转base64编码
outputPath: "assets/images", // 为你的文件配置自定义 output 输出目录
}
},
]
},
]
}
})
注意,如果使用了url-loader,建议scss和css中,去除cache-loader
的使用,不然第一次打包可能导致部分文件无法被打包到,因为优先使用缓存,缓存并没有。
scss中使用绝对路径引入图片
body {
background-image: url("~@/assets/images/emote/emoji/0afecaf3a3499479af946f29749e1a6c285b6f65.png");
}
使用~@
表示路径别名@
输出模板处理
因为typecho的主题是php开发的,除了涉及到多页面,我们还需要考虑php引入css、js文件的问题。
其实原理也很简单,我们通过ejs自定模板,将webpack打包后的文件对应拿到就行了,css.ejs处理css文件引入,script.ejs处理js文件引入
如果使用模板,我们需要先安装一个插件
yarn add HtmlWebpackPlugin --dev
HtmlWebpackPlugin处理多页面,一个页面传一个对象给他,他根据对象配置生成页面,然后因为多页面,所以一定要配置chunks属性,表示生成时只引入chunks对应的范围文件。
讲人话就是,我设置index,那么他就引入index入口文件中的需要使用到的资源文件。
//模板插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env, argv) => ({
//入口
entry: {
index: './src/pages/home/index.js',
about: './src/pages/about/index.js',
},
// 插件
plugins: [
new HtmlWebpackPlugin(
{
filename: '[name]/css.php',
template: resolve('./src/template/css.ejs'),
chunks: ['index'],
inject: false,
},
{
filename: '[name]/css.php',
template: resolve('./src/template/css.ejs'),
chunks: ['about'],
inject: false,
},
)
]
})
- filename:输出的文件名
- template:模板
- chunks:范围块
- inject:是否注入,如果是自定义模板,就不需要自动注入,我们自己写
filename的效果和output出口里面的filename效果一致。可以参考
template模板,由于webpack本来是用html文件做模板的,但是我们不用,用默认支持的ejs,方便for循环啥的,因为我查了半天只有ejs的自定义模板,html估计也行,但是没去试了。
<%=htmlWebpackPlugin.files.css[css] %>
<%=htmlWebpackPlugin.files.css[css] %>可以获取到输出的css文件引入的路径数组
<%= htmlWebpackPlugin.files.js[js] %>
<%= htmlWebpackPlugin.files.js[js] %>同理,也是js的路径数组
那么我们改动一下:
css.ejs
<% for (var css in htmlWebpackPlugin.files.css) { %>
<link rel="stylesheet" href="<?php $this->options->themeUrl('<%=htmlWebpackPlugin.files.css[css] %>'); ?>">
<% } %>
script.ejs
<% for (var js in htmlWebpackPlugin.files.js) { %>
<script src="<?php $this->options->themeUrl('<%= htmlWebpackPlugin.files.js[js] %>'); ?>"></script>
<% } %>
模板就ok了。
注意:
chunks不能在HtmlWebpackPlugin中出现两次,比如两个对象的chunks都是index,这是不行的,如果同一个chunks生成两份文件,那么只需要多new一次HtmlWebpackPlugin即可。
// 插件
plugins: [
new HtmlWebpackPlugin(
{
filename: '[name]/css.php',
template: resolve('./src/template/css.ejs'),
chunks: ['index'],
inject: false,
},
{
filename: '[name]/script.php',
template: resolve('./src/template/script.ejs'),
chunks: ['index'],
inject: false,
},
{
filename: '[name]/css.php',
template: resolve('./src/template/css.ejs'),
chunks: ['about'],
inject: false,
},
{
filename: '[name]/script.php',
template: resolve('./src/template/script.ejs'),
chunks: ['about'],
inject: false,
},
)
]
可以看到,这样的话,我们虽然可以生成index和about目录下的css.php和script.php文件,但是我们的写法就很繁琐了,重复的内容很多,我们优化一下。
优化自定模板
创建一个custom-template.js
文件,存放与项目根目录的webpack
目录下,以后webpack拆分出来的配置,都丢这个目录。
custom-template.js
/*
* @Author: mulingyuer
* @Date: 2021-07-04 01:29:59
* @LastEditTime: 2021-07-04 01:52:33
* @LastEditors: mulingyuer
* @Description: 自定义模板
* @FilePath: \JJ\webpack\custom-template.js
* 怎么可能会有bug!!!
*/
const path = require('path');
const resolve = function (myPath) {
return path.resolve(__dirname, myPath);
};
//模板插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
// css模板
const cssEjs = resolve('../src/template/css.ejs');
const cssTemplate = (...chunks) => ({
filename: '[name]/css.php', //输出的文件名
template: cssEjs, //自定义模板
chunks, //指定入口块
inject: false, //自定义模板不需要自动注入
minify: {
removeComments: true, //去除html注释
collapseWhitespace: true, //去除换行
minifyCSS: true //缩小样式元素和样式属性中css
},
});
//js模板
const jsEjs = resolve('../src/template/script.ejs');
const jsTemplate = (...chunks) => ({
filename: '[name]/script.php', //输出的文件名
template: jsEjs, //自定义模板
chunks, //指定入口块
inject: false, //自定义模板不需要自动注入
minify: {
removeComments: true, //去除html注释
collapseWhitespace: true, //去除换行
minifyCSS: true //缩小样式元素和样式属性中css
},
});
const chunksArr = ["index", "about"];
module.exports = function () {
//css
let cssPhp = [];
chunksArr.forEach(chunk => cssPhp.push(cssTemplate(chunk)));
//js
let jsPhp = [];
chunksArr.forEach(chunk => jsPhp.push(jsTemplate(chunk)));
//导出
return [cssPhp, jsPhp].map(arr => new HtmlWebpackPlugin(...arr));
}
这里多了个minify设置,具体都有注释,不多解释了,具体的封装也很简单,就是for循环得到数组而已,数组里面存放new HtmlWebpackPlugin,然后我们node的方式导出。
使用
webpack.config.js
//自定义模板
const customTemplate = require('./webpack/custom-template');
module.exports = (env, argv) => ({
// 插件
plugins: [
//多页面
...customTemplate()
]
})
这样就行了,简化了写法,也更加便于阅读
给文件加上哈希
既然我们现在资源文件可以自动设置了,那么hash就成了我们必备的东西了,hash有多香,当我们文件发生变化后,资源的hash也发生变化,那么资源名上使用hash,就有了一个天然的版本号一样,浏览器加载时,就会加载变动的最新的文件。
一般情况下,开发模式我们是不用hash的,因为浏览器f12之后有不缓存的选项,生产环境,就需要使用hash。
而且如果生产环境使用hash会导致webpack的HMR(热更新)失效,虽然我们不用,但是还是按照大众的整,随大流。
hash也有三个设置:
- [hash] - 本应用中任意文件更新,都会导致所有输出包名变化
- [chunkhash] - 本应用中有几个模块更新,导致几个模块包名变化
- [contenthash:8] - 本应用打包输出文件级别的更新,导致输出文件名变化
在生产环境中,一般推荐使用[contenthash:8]的
所以我们webpack的配置要这样改一下:
webpack.config.js
module.exports = (env, argv) => ({
// 出口
output: {
path: resolve('./dist'), //指定输出目录
filename: argv.mode === 'production' ? '[name]/index.[contenthash:8].js' : '[name]/index.js', //文件名
chunkFilename: argv.mode === 'production' ? '[id].[contenthash:8].js' : '[id].js'
},
// 插件
plugins: [
//抽取css
new MiniCssExtractPlugin({
filename: argv.mode === 'production' ? '[name]/style.[contenthash:8].css' : '[name]/style.css', //输出的css文件名,默认以入口文件名命名(例如main.css)
chunkFilename: argv.mode === 'production' ? '[id].[contenthash:8].css' : '[id].css', //公共样式??
}),
]
})
webpack配置拆分
利用webpack-merge
插件,他会通过算法,合并多个配置对象。
具体用法比较简单,但是写起来代码很多,因为需要调整整个webpack的配置结构,这里就放一下主入口的示例吧,具体自行到《JJ》主题github的dev分支查看具体代码。
webpack.config.js
/*
* @Author: mulingyuer
* @Date: 2021-07-01 22:17:24
* @LastEditTime: 2021-07-04 02:43:38
* @LastEditors: mulingyuer
* @Description:webpack配置文件
* @FilePath: \JJ\webpack.config.js
* 怎么可能会有bug!!!
*/
const path = require('path');
const resolve = function (myPath) {
return path.resolve(__dirname, myPath);
};
//公共配置
const commonConfig = require('./webpack/webpack.common');
//开发配置
const developmentConfig = require('./webpack/webpack.dev');
//生产配置
const productionConfig = require('./webpack/webpack.prod');
//配置合并
const { merge } = require('webpack-merge');
module.exports = (env, argv) => {
switch (argv.mode) {
case 'development':
return merge(commonConfig, developmentConfig);
case 'production':
return merge(commonConfig, productionConfig);
default:
throw new Error('No matching configuration was found!');
}
}
修复模板的小问题
使用ejs模板获取到css和js的引用后,他的路径会有点小问题,输出大概是这样的:
<link rel="stylesheet" href="<?php $this->options->themeUrl('../index/style.css'); ?>">
你会发现存在../
路径,我们得想办法去掉。
最简单的方式就是ejs里面处理就完事了,毕竟一个replace完事。
css.ejs
<% for (var css in htmlWebpackPlugin.files.css) { %>
<% const path = htmlWebpackPlugin.files.css[css].replace(/^\.\./,'dist'); %>
<link rel="stylesheet" href="<?php $this->options->themeUrl('<%= path %>'); ?>">
<% } %>
script.ejs
<% for (var js in htmlWebpackPlugin.files.js) { %>
<% const path = htmlWebpackPlugin.files.js[js].replace(/^\.\./,'dist'); %>
<script src="<?php $this->options->themeUrl('<%= path %>'); ?>"></script>
<% } %>
就这样搞定
自动获取入口配置
每次都手动写入口文件实在太累,它的文件格式都是一样的,加上我们可以固定入口文件的层级,可以使用glob来获取指定文件的路径
以《JJ》主题为例,我们的入口文件全部在:src/pages/xxx/index.js
路径下,不同的页面,用不同的文件夹保存。
例:
- src/pages/index/index.js
- src/pages/about/index.js
那么我们就需要通过glob获取到pages目录下,所有符合要求的index.js入口文件
yarn add glob --dev
glob本身默认方式是异步的,因为读取文件在node的设计里,都是异步,通过回调来进一步处理,但是异步的话明显不合适用于我们webpack的入口文件读取。
因为webpack是同步运行的,你异步任务除非封装promise做async,否则webpakck运行完毕了,你文件还未读取到,很尴尬,所以我们要使用glob的同步方法:glob.sync
创建一个用于自动读取入口文件的js:auto-entry.js
auto-entry.js
const path = require('path');
const resolve = function (myPath) {
return path.resolve(__dirname, myPath);
};
const glob = require("glob");
const entryPath = "../src/pages/";
let fileObj = [];
function readFile() {
try {
const globArr = glob.sync("**/index.js", { cwd: resolve(entryPath) });
if (globArr.length) fileArr = globArr;
} catch (error) {
throw error;
}
};
readFile();
module.exports = {
//入口文件
entry() {
let obj = {};
fileArr.forEach(item => {
const arr = item.split("/");
obj[arr[0]] = resolve(`${entryPath}${item}`)
});
return obj;
},
//chunks
chunks() {
return fileArr.map(item => item.split("/")[0]);
}
}
glob.sync
第一个参数是匹配的路径参数,官方中**
两个星号可以搜索匹配项的目录和子目录,也就说可以匹配多层级。
第二个参数是一个对象,因为我们要指定匹配某个目录下,不需要全局匹配,所以指定cwd
的参数为pages目录。
glob.sync获取都返回的是一个文件路径数组,例:
['index/index.js','about/index.js']
这样就已经符合我们的需要的,通过js拼接成入口文件,方法如上的entry()
除了入口,我们还可以搞定htmlWebpackPlugin
插件使用的chunks参数,只要拿到每个路径/
符号前面的那个数据即可了,就可以获取到对应的块名,毕竟我们是根据文件夹区分的
使用的时候,我们在之前拆分出来的webaack配置中调整一下:
custom-template.js
//入口文件
const autoEntry = require('./auto-entry');
// const chunksArr = ["index", "about"];
const chunksArr = autoEntry.chunks();
在自定义模板文件中,引入auto-entry,然后替换掉chunksArr 即可
webpack.common.js
//入口文件
const autoEntry = require('./auto-entry');
module.exports = {
//入口文件
entry:{
...autoEntry.entry()
}
}
通用webpack配置中,将入口文件替换为auto-entry的entry方法。
至此,自动设置入口文件完成
babel配置支持async/await和class
yarn add @babel/plugin-proposal-class-properties @babel/plugin-transform-runtime @babel/runtime-corejs3 --dev
在项目根目录创建.babelrc
文件,内容如下
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
],
"@babel/plugin-proposal-class-properties"
]
}
.babelrc是用于配置babel的,所以我们在webpack的js配置loader时,可以调整一下
// js
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/, // 不处理该文件夹下的js文件
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true, //开启缓存
}
},
},
这样就可以使用es6的一些语法了
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据