图片处理

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也有三个设置:

  1. [hash] - 本应用中任意文件更新,都会导致所有输出包名变化
  2. [chunkhash] - 本应用中有几个模块更新,导致几个模块包名变化
  3. [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-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路径下,不同的页面,用不同的文件夹保存。

例:

  1. src/pages/index/index.js
  2. 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的一些语法了

分类: webpack5 标签: webpack

评论

暂无评论数据

暂无评论数据

目录