2天时间,聊今年最热的 Agent、上下文工程、AI 产品创新等话题。2025 年最后一场~ 了解详情
写点什么

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

评论

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

MobTech MobLink|裂变拓新,助力运营

MobTech袤博科技

尚硅谷CDH 6.3.2视频教程发布

小谷哥

PCB为什么常用50Ω阻抗?6大原因

华秋PCB

科普 电路 阻抗 PCB PCB设计

没有研发过程数字化,DevOps就是水中月、雾中花

行云创新

DevOps 研发管理 云原生IDE

AIGC爆火的背后需要掌握的基础原理

飞桨PaddlePaddle

人工智能 AI 百度飞桨 AIGC

一站式指标平台 Kyligence Zen 登陆亚马逊云科技 Marketplace

Kyligence

数据分析 指标中台

图文介绍 Windows 系统下打包上传 IOS APP 流程

ios 开发

天天预约|如何使用「代预约」功能?全在这篇文章里!

天天预约

线上预约 预约工具 预约 预约小程序

实力担当!焱融文件存储再次中标中国移动项目

焱融科技

#高性能 #分布式文件存储 #文件存储 #中国移动

跟ChatGPT聊天、需求润色优化,禅道OpenAI 插件发布!

禅道项目管理

项目管理 openai ChatGPT

阿里云 EMAS & 魔笔:3月产品动态

移动研发平台EMAS

阿里云 DevOps 测试 低代码开发 移动端开发

实践分享:如何在自己的App 中引入AI画图!

FN0

小程序 小程序容器 AI绘画

软件测试/测试开发丨容器编排K8S 下部署分布式UI自动化解决方案

测试人

k8s 软件测试 #Kubernetes#

升级企业数智化底座,用友iuap助力企业高质量发展

用友BIP

用友 技术大会 iuap平台

被吐槽 GitHub仓 库太大,直接 600M 瘦身到 6M,这下舒服了

程序员小富

Java git

中南财经政法大学教授施先旺:事项法会计促进业财合一和会计变革

用友BIP

技术大会 业财合一 业财融合 事项会计

理一理事务实现

Zhang

MySQL 事务 数据库·

什么是文件传输协议,文件传输协议又是怎么工作的

镭速

540p秒变1080p!小红书端侧实时超分带你免流量玩嗨短视频

小红书技术REDtech

AI 算法 短视频

低代码开发,是稳打稳扎还是饮鸩止渴?

引迈信息

前端 低代码 JNPF

来2023用友BIP技术大会,与北京地铁等领先企业探索数智化转型路径

用友BIP

技术大会 用友iuap 用友技术大会 数智底座 技术底座

想让 ChatGPT 帮忙进行数据分析?你还需要做......

Kyligence

数据分析 指标平台

小红书自研小程序:电商体验与效果优化的运行时体系设计

小红书技术REDtech

架构 前端

京东技术专家首推:微服务架构深度解析,GitHub星标120K

程序知音

Java 微服务 springboot java架构 Java进阶

【一行代码秒上云】Serverless六步构建全栈网站

华为云开发者联盟

云计算 华为云 华为云开发者联盟 企业号 4 月 PK 榜

人人可用的敏捷指标工具!Kyligence Zen 正式发布 GA 版

Kyligence

数据分析 Kyligence Zen 指标平台 大数据管理

从一场文学奖评选,看金山文档To B 转型怎么走

B Impact

AIGC:数字内容创新的新引擎,还有藏着更多你知道的细节

加入高科技仿生人

人工智能 AI AIGC

软件测试/测试开发丨UI自动化测试,PageObject设计模式

测试人

软件测试 自动化测试 测试开发 UI自动化 pageobject

在高并发场景下保证数据一致性:sync.Map的并发安全性实践

Jack

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