阿里云「飞天发布时刻」2024来啦!新产品、新特性、新能力、新方案,等你来探~ 了解详情
写点什么

巧用 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:211008

评论

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

Gartner最新报告,分析超大规模边缘解决方案

阿里云视频云

云计算 边缘计算 边缘云

简化你的代码,提高生产力:这10个Lambda表达式必须掌握

做梦都在改BUG

Java Lambda

LED显示屏十大应用领域值得你收藏

Dylan

LED显示屏 户外LED显示屏 户内led显示屏

一份深入解析Java虚拟机HotSpot手册,让我卷成美团架构师

程序知音

Java JVM java架构师 hotspot Java进阶

软件测试/测试开发丨H5性能分析实战

测试人

软件测试 性能测试 自动化测试 H5 W3C

特斯拉和OpenAI的加持,马斯克简直人生赢家

这我可不懂

人工智能 低代码 马斯克 新能源

构建系列之前端脚手架vite

江湖修行

Vue vite cli

详解事务模式和Lua脚本,带你吃透Redis 事务

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 4 月 PK 榜

探索网络世界的核心:TCPIP协议四层模型解析

做梦都在改BUG

Java 计算机网络 网络协议 TCP/IP

云原生:驱动企业数字化新模式

北京好雨科技有限公司

云原生 数字化 rainbond 企业号 4 月 PK 榜

华为云开源项目OpenTiny的TinyNG组件库的设计理念是什么?

英勇无比的消炎药

前端 开源项目 OpenTiny UI组件库

企业云管平台是什么?有什么用?

行云管家

云计算 云管理 云管

[直播预告]行云管家堡垒机V7.0版本说明会

行云管家

行云管家

成都开发者Meetup|聚焦云原生开源,点亮企业创新活力

阿里巴巴云原生

阿里云 容器 微服务 云原生

文献管理软件:EndNote 20 v20.5激活版

真大的脸盆

Mac Mac 软件 文献管理 文献管理工具

如何用 YonBuilder 构建线索管理应用?

YonBuilder低代码开发平台

MySQL数据库和Redis缓存一致性的更新策略

做梦都在改BUG

Java MySQL 数据库 redis 缓存

阿里P8架构师20年经验总结成微服务设计企业架构转型之道笔记

程序知音

Java 微服务 java架构 Java进阶 后端技术

真下饭!字节技术官DDD(领域驱动设计)手册,拆解业务代码首选

做梦都在改BUG

Java 架构 领域驱动设计 DDD

喜讯!索信达荣获CCSA TC601年度“优秀成员单位”

索信达控股

【重磅发布】易观千帆用户体验分析产品领先版正式发布

易观分析

金融 银行 用户分析

雀氏牛,Alibaba分布式微服务成长手册,GitHub已开源

做梦都在改BUG

Java 分布式 微服务

什么是可信云?通过可信云评估意味着什么?

行云管家

可信云

又搞事!阿里400页JDK并发源码指南,再次被GitHub置顶了!

做梦都在改BUG

Java jdk 多线程 高并发 源码剖析

面试官:说一说mysql的varchar字段最大长度?

程序员小毕

MySQL 数据库 程序员 面试 架构师

SpringBoot整合RocketMQ,老鸟们都是这么玩

做梦都在改BUG

Java RocketMQ Spring Boot

玩转Github:三分钟教你如何用 Github 快速找到优秀的开源项目

程序知音

Java GitHub 编程语言 后端技术

大模型高效开发的秘密武器:大模型低参微调套件MindSpore PET

华为云开发者联盟

人工智能 华为云 大模型 华为云开发者联盟 企业号 4 月 PK 榜

Apifox:API 接口自动化测试完全指南

Apifox

测试 自动化测试 测试工具 接口工具免费 免费工具

华为云发布多项场景化解决方案助力制造业企业加速上云

IT科技苏辞

“字节”再次起跳!内部651页剖析HotSpot 源码手册,GitHub已开源

做梦都在改BUG

Java JVM 虚拟机 hotspot

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