阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

巧用 webpack loader 实现项目的定制化

  • 2019-09-16
  • 本文字数:3572 字

    阅读完需:约 12 分钟

巧用 webpack loader 实现项目的定制化

随着前端技术的发展,Web 应用变得复杂。为解决开发的复杂度,前端开发也有了模块化的概念。使用 Webpack 完成 模块化的打包构建的方案,可谓尽人皆知。但是利用 Webpack 能做的事情远不止如此。这篇文章从一个独特的角度,利用 Webpack 的特点实现了定制化需求,希望能够对大家有一些启发。

有这样的需求:项目交付的给客户时,需要支持针对客户定制产品的 LOGO、登录界面的背景。

简单分析

手动替换图片文件再编译的方法肯定是无法接受的。


如果你说采用分支的方式来实现这种需求,我觉得也是不太现实。毕竟,这并不是分支的使用场景。


项目在交付时需要避免交付的代码中包含其他客户的资源和信息。这意味着,通过配置文件等在运行时加载的方式是行不通。


想来想去,问题的本质其实是解决项目编译输出时 CSS 可以使用我们指定的图片文件,而我们需要将这个过程自动化。

第一种方案

先来一种简单而又直接的方案:直接替换。其步骤如下:


  • 将图片资源放入指定的目录中,按项目 ( 客户 ) 区分。

  • 执行替换图片资源的脚本,使用指定的资源替换。

  • 执行项目的编译命令。


 1// pre-packaging.js 2 3const path = require("path"); 4const fs = require("fs"); 5const project = process.argv[2]; 6const distPath = path.resolve("./src/static/images"); // 源代码目录 7const resourcePath = path.resolve("./resources", project); // 项目静态文件目录 8 9function copyDir(src, dist) {10  try {11    fs.accessSync(dist, fs.constants.R_OK | fs.constants.W_OK);12  } catch (err) {13    fs.mkdirSync(dist);14  }1516  const copyFile = (src, dist) => {17    fs.createReadStream(src).pipe(fs.createWriteStream(dist));18  };1920  const dirList = fs.readdirSync(src);2122  dirList.forEach(item => {23    const currentPath = path.resolve(src, item);24    const currentDistPath = path.resolve(dist, item);2526    if (fs.statSync(currentPath).isDirectory()) {27      copyDir(currentPath, currentDistPath);28    } else {29      const src = currentPath;30      const dist = currentDistPath;3132      copyFile(src, dist);33    }34  });35}3637copyDir(resourcePath, distPath);
复制代码


执行脚本


1node ./pre-packaging.js projectname
复制代码


看起来我们的问题已经得到解决。但是你仔细想想,便会发现,这种方案存在多个不足之处:


  • 侵入性强。每次自定义版本构建之后都修改目录中的图片资源,这些修改很容易被同步到远端。

  • 拓展性差。自定义的图片资源必须严格按照源码中的约定,比如图片格式,图片尺寸。每一张图片都需要在代码中提供相应的插槽。

  • 功能单一。只能修改图片的引用,当其他的样式需要调整时便无能为力。

  • 体验性差。将构建过程拆分为准备静态资源和编译两个过程。

第二种方案

是否有更好的方案?此时我们回到问题:如何实现同一个项目针对不同客户定制界面的 Logo 和登录背景?


我们需要修改的是什么?CSS!


既想修改 CSS 样式,又想不对源码进行修改,那只有采用 CSS 样式具有的覆盖规则来实现。源文件中设置默认样式,约定使用的 CSS 选择器,通过编译将新的样式文件和源文件合并,所有的样式打包输出。


这种方式有诸多好处:


  • 侵入性弱。只需要在项目仓库中维护对应的资源,不影响源代码,交付时也不会包含多余的资源。

  • 拓展性强。自定义的图片资源不在依赖源码,可以使用任意的图片格式。

  • 功能丰富。可以额外增加自定义样式,不限于需求中的 Logo 和背景。

  • 体验好。在编译阶段加载指定的样式,一步到位。


说到前端的编译打包,自然想到 Webpack。可以从 Webpack Loader 入手,实现上述过程。

Webpack Loader

在 Webpack 的生态中,Loader 用于对模块的源代码进行转换。Loader 可以使你在 import 或"加载"模块时预处理文件。因此,Loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。Loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。


Webpack Loader 的编写可参考官方文档,有非常详细的说明。


以常见的一段 Webpack 配置为例:


 1module.exports = { 2  entry: [...], 3  output: {...}, 4  module: { 5    rules: [ 6      ..., 7      { 8        test: /\.less$/, 9        use: [10          {11            loader: 'style-loader',12          },13          {14            loader: 'css-loader',15          },16          {17            loader: 'less-loader',18          }19        ];20      }21      ...,22    ],23  },24};
复制代码


上述配置在执行过程中,less 文件的编译会按照如下顺序 (Webpack Loader 执行顺序):



在整个编译过程中,我们可以在每一个 Loader 的开始前和结束后合并我们自定义样式,如下图所示:



在 less-loader 之前加入自定义的 CSS 样式是最好的时机,为什么呢?有两点:


  • 同时支持 CSS 和 Less 两种文件。

  • 在整个编译开始之前加入,对编译的整个过程没有影响。新增的样式同样享受完整编译过程。


编译过程修改为如下图所示:


开发一个 merge-loader

在目前的场景中,merge-loader 只需要一个参数:自定义样式的文件路径。所以 Webpack 配置文件可以修改为:


 1const { getOptions } = require('loader-utils'); 2 3module.exports = function (source) { 4  const options = getOptions(this); 5  const { style } = options; 6 7  // 读取样式文件,返回字符串 8  const string = fs.readFileSync(style); 910  // 合并到原始文件,返回给下一个loader11  source += string;1213  return source;14};
复制代码


你以为这样就结束了?不,上述逻辑有两个问题还需优化:


  • 当样式中存在图片的引用时,以字符串形式拼接在源码样式中会遇到图片路径错误的问题。

  • 只要文件通过了规则/.less&/的匹配,就会执行一次合并的操作。含有的 vue 文件也会触发这个规则(虽然重复引用不会增加代码量)。


这两个问题的解法如下:


  • 使用 @import “path/of/style” 方式合并样式文件。其他的处理交给后面的 Loader,保证文件和图片路径引用正确。

  • 增加一个参数 target,指定一个文件作为 merge 的对象。


这样一来,merge-loader 的逻辑修改如下:


 1module.exports = { 2  entry: [...], 3  output: {...}, 4  module: { 5    rules: [ 6      ..., 7      { 8        test: /\.less$/, 9        use: [10          {11            loader: 'style-loader',12          },13          {14            loader: 'css-loader',15          },16          {17            loader: 'less-loader',18          },19          {20            loader: path.resolve(__dirname, './loader/merge-less.js'), // 自定义loader文件的路径21            options: {22              style: path.resolve(root, 'client/statics/projects/it/style.less'),23            },24          }25        ];26      }27      ...,28    ],29  },30};
复制代码

优化 Loader

最后利用 Loader 工具库 来优化代码


 1const fs = require('fs'); 2const path = require('path'); 3const loaderUtils = require('loader-utils'); 4const validateOptions = require('schema-utils'); 5 6const schema = { 7  type: 'object', 8  properties: { 9    style: {10      type: 'string',11    },12    target: {13      type: 'string',14    },15  },16  required: [ 'style', 'target' ],17};181920module.exports = function (source, meta) {21  const options = loaderUtils.getOptions(this);2223  // 验证 options 参数24  validateOptions(schema, options, 'Loader options');2526  let { style, target } = options;2728  /*29   * Loader 原则之一:不要在模块代码中插入绝对路径,因为当项目根路径变化时,文件绝对路径也会变化30   * 使用 stringifyReques 将绝对路径转换成相对路径31   */32  style = loaderUtils.stringifyRequest(this, style);3334  if (meta) {35    const { file, sourceRoot } = meta;3637    if (target === path.join(sourceRoot, file)) {38      const string = `\n @import ${style};\n`;3940      source += string;41    }42  }4344  return source;45}
复制代码

结束

借助 Webpack Loader,已经完成了项目的定制化。这种方案的几个特点:


  • 侵入性弱。只需要在项目仓库中维护对应的资源,不影响源代码,交付时也不会包含多余的资源。

  • 拓展性强。自定义的图片资源不在依赖源码,可以使用任意的图片格式。

  • 功能丰富。可以额外增加自定义样式,不限于需求中的 Logo 和背景。

  • 体验好。在编译阶段加载指定的样式,一步到位。


本文转载自公众号滴滴技术(ID:didi_tech)。


原文链接:


https://mp.weixin.qq.com/s/7SRBH5m7DjUicLxG_QHgIg


2019-09-16 22:211005

评论

发布
暂无评论
发现更多内容

22个每个程序员都应该知道的 Git 命令

Java-fenn

Java

字节架构师:来说说 Kafka 的消费者客户端详解,你都搞懂了吗?

Java快了!

Kafk

GreatSQL vs MySQL性能测试来了,速围观~

Java-fenn

Java java;

大厂工程师如何给SpringBoot封装响应数据和异常处理?

Java-fenn

Java

Fast.Framework ORM 于中秋节后 正式开源

Java-fenn

Java

设计模式之工厂方法模式--更加符合开闭原则的工厂模式

Java-fenn

Java

九个写 TypeScript 的坏习惯,看看你有没有?

Java-fenn

Java

LeetCode-1002. 查找常用字符(Golang)

bug菌

9月日更 Leet Code 9月月更

开箱即用,40 个 SpringBoot 常用注解!

Java-fenn

Java 多线程:锁

Java-fenn

Java

C++最佳实践 | 6. 性能

Java-fenn

Java

详解容灾架构中的数据复制技术

Java-fenn

Java

C++ 20 协程 Coroutine之剖析

Java-fenn

Java

数据治理(十四):Ranger同步Linux用户

Lansonli

数据治理 数据安全 9月月更

HTTPS TLS 1.2

懒时小窝

https HTTP

太赞了,使用应用魔方 AppCube,我没有写一行代码就开发出了一款投票微信小程序

wljslmz

微信小程序 低代码开发 AppCube 9月月更

库调多了,都忘了最基础的概念-《单例模式VS状态码》

知识浅谈

volatile 单例模式 9月月更

【全网最全】你绝对没见过的,Alibaba内部的18份Java技术体系教程

程序知音

Java JVM 阿里 程序员面试 后端技术

SAP 电商云 Spartacus UI 同 SAP Customer Data Cloud 的集成

Jerry Wang

typescript SAP Hybris Spartacus 9月月更

Lombok、JUnit——开发工具插件

胖虎不秃头

maven java; 9月月更

树莓派部署Elasticsearch6集群

程序员欣宸

elasticsearch 9月月更

Java进阶(十五)Java中设置session的详细解释

No Silver Bullet

Java session Cookie 9月月更

阿里前端常见面试题(附答案)

loveX001

前端 前端javascript

分布式锁有哪些实现方案各自有什么优缺点?

想要飞的猪

分布式锁 redis分布式锁 zookeeper分布式锁

Plasticine: 面向并行模式的可重配架构

俞凡

架构 网络

LeetCode-1370. 上升下降字符串(Java)

bug菌

9月日更 Leet Code 9月月更

什么是X态传播?

Java-fenn

Java

【JavaWeb】Filter系列——过滤器有什么用,如何编写?Filter过滤器的设计模式

胖虎不秃头

java; 过滤器 9月月更

Maven——自动化构建工具

胖虎不秃头

Web java; 9月月更

全到哭!从面试到架构,阿里大佬用五部分就把高并发编程讲清楚了

Java全栈架构师

Java 程序员 面试 程序人生 高并发

知道这些坑,你还敢乱把单体架构拆成分布式吗

Java-fenn

Java

巧用 webpack loader 实现项目的定制化_文化 & 方法_张伦_InfoQ精选文章