重构案例:将纯HTML/JS项目迁移到Webpack

news2024/12/24 10:12:55

我们已经了解了许多关于 Webpack 的知识,但要完全熟练掌握它并非易事。一个很好的学习方法是通过实际项目练习。当我们对 Webpack 的配置有了足够的理解后,就可以尝试重构一些项目。本次我选择了一个纯HTML/JS的PC项目进行重构,项目位于 GitHub 上,非常感谢该项目的贡献者。

重构案例选择了两个页面:首页 index.html 和购物车页面 cart.html。

在这里插入图片描述

项目目录结构清晰,根目录包含了各个 HTML 页面,同一层级下还有 CSS、JS 和 IMG 文件夹。每个 HTML 页面对应各自的 CSS 和业务 JS 文件。

在这里插入图片描述

初始化 npm 项目

首先,创建一个新的空文件夹,并在其中运行 npm init -y 命令来初始化项目。接着,在项目根目录下创建 src 文件夹,将 index.html 文件复制到 src 目录下,以此为基础进行重构。打开 index.html 文件,可以看到页面中引入了 CSS、图片和 JS 资源。然后将 CSS、IMG 和 JS 文件夹也移至 src 目录下。

在这里插入图片描述

随后,我们观察 index.html 文件中的 <link><script> 标签,它们分别用于加载外部的 CSS 文件和 JavaScript 文件。为了使项目更好地适应 Webpack 的模块化打包机制,在 index.html 同一目录级别的位置创建一个新的 index.js 文件。在这个新的 index.js 文件中,我们将使用模块化的方式导入原本通过 <link> 标签引入的 CSS 文件以及通过 <script> 标签加载的 JavaScript 文件。

对于那些直接嵌入在 <script> 标签内的脚本代码,例如图中提到的 flexslider 函数,我们暂且保持其原样,不做变动。

import "./css/public.css";
import "./css/index.css";

import './js/jquery-1.12.4.min.js'
import './js/public.js';
import './js/nav.js';
import './js/jquery.flexslider-min.js';

初始化 webpack

使用命令 npm install webpack --save 来安装 Webpack,并创建 webpack.config.js 文件来定义基本的配置。由于原项目包含多个 HTML 页面,因此这是一个多入口项目。

const path = require("path");
module.exports = {
  mode: "development",
  entry: {
    index: "./src/index.js",
  },
  output: {
    filename: "[name].[hash:8].js",
    path: path.join(__dirname, "./dist"),
  },
};

package.json 中添加 "build": "webpack" 命令。

处理css、图片

Webpack 默认不支持处理 CSS 和图片资源。要处理 CSS 资源,可以通过 css-loaderstyle-loader;而图片资源则可以通过 Webpack 5 的内置功能——asset module 来处理。

首先,安装处理 CSS 所需的依赖项:

npm install css-loader style-loader --save

这里我们使用 css-loader 来解析 CSS 文件,并通过 style-loader 将其作为内联样式插入到 DOM 中。初期阶段,我们可以先这样创建内联样式,之后再考虑将 CSS 资源进一步抽离优化。

module.exports = {
  module: {
    rules: [
      { test: /\.css$/, use: ["style-loader", "css-loader"] },
      { test: /\.(jpg|jpeg|png|gif|svg)/i, type: "asset" },
    ],
  },
}

处理 html

使用 html-webpack-plugin 插件根据 index.html 创建压缩后的 HTML 文件,并将编译后的 JS 文件引入。

const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
 plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      filename: "index.html",
    }),
  ],
}

图片资源

asset module 主要用于处理在 CSS 文件中通过背景图像或其他方式引入的图片资源。然而,对于 HTML 页面中直接通过标签引入的资源,它则无能为力。

在这里插入图片描述

如图所示,这些图片的路径都是 img/xxx.png。由于编译后的文件位于 dist 文件夹下,而此时 dist 文件夹下没有 img 目录。因此,我们可以通过 copy-webpack-plugin 将 src 目录下的 img 文件夹复制到 dist 目录下。

const CopyPlugin = require("copy-webpack-plugin");

module.exports = {
  plugins: [
    new CopyPlugin({
      patterns: [
        {
          from: "./src/img",
          to: "./img",
        },
      ],
    }),
  ],
};

这样一来,当我们执行 npm run build 时,dist 文件夹中已经生成了 index.html 及其对应的 CSS、JS 和图片等资源。然而,当我们尝试从 index.html 打开页面时,却发现页面报错提示 $ 未定义,并且页面底部定义的 flexslider 方法并未生效。

在这里插入图片描述

ProvidePlugin $ 符号

我们知道 $ 符号实际上是 jQuery 提供的一个全局变量。提示找不到 $ 符,意味着 jQuery 的全局变量尚未正确暴露。为了解决这个问题,我们可以按照以下步骤操作:

首先,通过安装 jQuery

npm install jquery --save

接着,调整 index.js 文件中的引入方式:

// 修改前
import './js/jquery-1.12.4.min.js'

// 修改后
import 'jquery';

然后,使用 ProvidePlugin 来定义 $ 的映射关系:

const webpack = require("webpack");
module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery",
    }),
  ],
};

最后,将 index.html 文件底部通过 <script> 标签调用的 flexslider 函数代码移动到需要引入的业务 JS 文件中。

完成上述步骤后,再次执行 npm run build,原有的 index.html 功能就能实现基本的重构,接下来就可以进行更多的优化工作了。

自动清空编译后文件夹

在执行 npm run build 时,Webpack 会根据 webpack.config.js 中的规则,在 dist 目录下生成编译后的文件。为了避免 dist 文件夹中生成的文件混杂在一起,通常我们需要在每次编译前手动清理该目录。

为了省去这一手动操作的麻烦,我们可以使用 clean-webpack-plugin 来自动清空 dist 文件夹。这样可以确保每次构建时,dist 目录都是干净的,从而避免旧文件的干扰。

const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
  plugins: [
    new CleanWebpackPlugin()
  ],
};

抽离css文件

这样会导致 JS 文件体积过大,并且 JS 和 CSS 代码混合在一起,不够清晰。在开发环境中,这种方式是可行的,因为编译速度快,但在生产环境中,我们需要将 CSS 资源抽离成单独的文件。

在这里插入图片描述

为此,我们可以使用 mini-css-extract-plugin 替换掉 style-loader,以实现 CSS 资源的独立打包。

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  module: {
    rules: [
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].[hash:8].css",
      chunkFilename: "[name].[hash:8].css",
    }),
  ],
};

通过这种方式,CSS 资源会被单独打包成一个文件,从而使得最终的输出更加规范和高效。如图所示,CSS 文件已经被独立出来。

在这里插入图片描述

js和css压缩

在前面的配置中,mode 被设置为 development,这在开发模式下便于调试。然而,在代码发布时,我们需要切换到 production 模式。在这种模式下,Webpack 会自动对资源文件进行压缩,以减小文件大小。

除了更改 mode 设置之外,我们还可以利用 terser-webpack-plugincss-minimizer-webpack-plugin 分别对 JavaScriptCSS 资源进行进一步的压缩。

const TerserPlugin = require("terser-webpack-plugin");
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  mode: "production",
  optimization: {
    minimizer: [new TerserPlugin({}), new CssMinimizerWebpackPlugin()],
  },
};

如图所示,我们可以看到不同 mode 设置下,以及使用插件对代码资源进行压缩后的文件体积变化。尽管当前项目只有一个页面,包含少量的 HTML、CSS 和 JS 文件,因此代码压缩的效果可能不是特别显著,但随着项目规模的扩大,这种压缩策略的效果将更加明显。

在这里插入图片描述

增加开发模式

以上代码的修改,我们都通过执行 npm run build 来观察编译后的产物。然而,当需要迁移多个文件时,使用开发模式会更便于实时查看所有业务场景的使用情况。

为了实现这一点,我们可以使用 webpack-dev-server 来启动一个开发服务器。安装完成后,在 webpack.config.js 文件中增加 devServer 的配置:

module.exports = {
  devServer: {
    open: true,
    compress: true,
    port: 8000,
  },
};

接着,在 package.json 文件中配置一个用于启动开发服务器的脚本指令:

"scripts": {
"dev": "webpack serve",
},

这样一来,通过执行 npm run dev 即可启动开发服务器,并自动打开浏览器查看 index.html 页面的内容。这样不仅方便调试,还能实时预览代码改动的效果。

多入口

到目前为止,我们仅迁移了首页的资源。现在我们将继续迁移购物车页面。与首页的迁移类似,首先将 cart.html 文件复制到 src 目录下,并查找其中引入的 CSS 和 JS 资源。

在这里插入图片描述

接着,创建一个 cart.js 文件,并在其中引入所需的 JS 和 CSS 文件:

// cart.js
import "./css/public.css";
import "./css/proList.css";

import 'jquery';
import './js/public.js';
import './js/pro.js';
import './js/cart.js';

接下来的配置非常关键。我们需要在 webpack.config.js 中定义多入口,并为每个页面生成相应的模板 HTML 文件。这里需要注意的是,一定要定义 chunks 属性,否则生成的 HTML 页面会错误地引入所有 CSS 和 JS 文件。

module.exports = {
  entry: {
    index: "./src/index.js",
    cart: "./src/cart.js",
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      filename: "index.html",
      chunks: ["index"],
    }),
    new HtmlWebpackPlugin({
      template: "./src/cart.html",
      filename: "cart.html",
      chunks: ["cart"],
    }),
  ],
};

完成上述配置后,再次执行 npm run build,即可编译出两个页面。此时,在 dist 文件夹中直接点击 cart.html 文件,也可以顺利访问页面内容。

拆分公共资源

尽管目前可以编译出两个 HTML 页面的资源,但如果查看 dist 文件夹下的 index.js 或者 cart.js 文件,会发现里面仍然包含有 jQuery 的代码。

在这里插入图片描述

为了优化这种情况,我们希望将像 jQuery 这样的重复资源作为公共模块来引用,而不是让它们在不同的 JS 文件中反复编译。以下是一个详细的 splitChunks 配置示例,它可以将 node_modules 中的资源进行分类处理,将 jQuery 编译成单独的文件,并将其他第三方库编译为另一个文件。

module.exports = {
  optimization: {
    splitChunks: {
      chunks: "all",
      name: "common",
      cacheGroups: {
        jquery: {
          // 测试模块是否包含 'jquery' 字符串
          test: /[\\/]node_modules[\\/]jquery[\\/]/,
          // 设置文件名
          name: "jquery",
          // 文件名可以是函数形式,也可以直接指定字符串
          filename: "jquery.js",
          // 确保只包含异步加载的 chunk 中的 jQuery
          priority: 10, // 可以设置优先级来控制合并顺序
          enforce: true, // 强制创建这个 chunk 即使其他规则可能忽略它
        },
        vendors: {
          // 这个 cache group 用来处理其他第三方库
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          priority: -10,
          filename: "vendors.js",
          chunks: "all",
        },
      },
    },
  },
};

由于当前项目中只用到了 jQuery 这一资源,因此只有 jQuery 被单独打包。随着项目发展和资源的增加,可以进一步细化拆分规则。从结果可以看出,当 jQuery 被拆分出来后,index.js 和 cart.js 的文件体积都有了显著的减少。

在这里插入图片描述

模板文件ejs

在不同的页面中,页面顶部的导航通常是固定且相同的。在当前项目中,相同的 HTML 部分是通过复制来定义的。为了提高代码的复用性和维护性,我们可以使用 EJS(Embedded JavaScript)来将这部分相同的逻辑抽离出来。

在这里插入图片描述

首先,在 src 文件夹下创建一个 ejs 文件夹,并在其中创建一个 header.ejs 文件。找到定义 header 的代码,将其复制到 header.ejs 文件中,并将变化的内容(如页面标题)通过 <%= title %> 的方式定义。

然后,在原来 HTML 页面中定义 header 代码的地方引入 header.ejs 文件,并传入动态变量:

<%=require('./ejs/header.ejs')({ title: '首页'})%>

由于 Webpack 本身不具备处理 EJS 文件的能力,因此我们需要安装 ejs-loader 并配置相应的处理规则:

module.exports = {
  module: {
    rules: [
      { test: /\.ejs/, loader: "ejs-loader", options: { esModule: false } },
    ],
  },
};

通过这样的配置,我们就实现了公共代码的复用。

以上步骤完成了从纯 HTML/JS 项目迁移到使用 Webpack 进行开发的全过程。通过使用 Webpack,我们实现了代码分割、资源按需加载,并采用了模块化开发。借助 html-webpack-plugin 和 clean-webpack-plugin 等插件,简化了构建流程,确保每次构建都能得到干净且优化的输出文件。

通过 EJS 抽象公共头部等重复代码片段,减少了冗余,提高了代码复用率,使代码库更简洁。

如果你对前端、JavaScript 和工程化感兴趣,快来瞅瞅我的其他文章吧~我会不定期分享各种学习心得和使用技巧。戳我的头像,一起探索更多好玩的内容吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2224083.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习案例:带有一个隐藏层的平面数据分类

该案例来自吴恩达深度学习系列课程一《神经网络和深度学习》第三周编程作业&#xff0c;作业内容是设计带有一个隐藏层的平面数据分类。作业提供的资料包括测试实例&#xff08;testCases.py&#xff09;和任务功能包&#xff08;planar_utils.py&#xff09;&#xff0c;下载请…

Jetpack架构组件_LiveData组件

1.LiveData初识 LiveData:ViewModel管理要展示的数据&#xff08;VM层类似于原MVP中的P层&#xff09;&#xff0c;处理业务逻辑&#xff0c;比如调用服务器的登陆接口业务。通过LiveData观察者模式&#xff0c;只要数据的值发生了改变&#xff0c;就会自动通知VIEW层&#xf…

Flutter TextField和Button组件开发登录页面案例

In this section, we’ll go through building a basic login screen using the Button and TextField widgets. We’ll follow a step-bystep approach, allowing you to code along and understand each part of the process. Let’s get started! 在本节中&#xff0c;我们…

【Python爬虫系列】_031.Scrapy_模拟登陆中间件

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)教程合集 👈👈…

ArcGIS001:ArcGIS10.2安装教程

摘要&#xff1a;本文详细介绍arcgis10.2的安装、破解、汉化过程。 一、软件下载 安装包链接&#xff1a;https://pan.baidu.com/s/1T3UJ7t_ELZ73TH2wGOcfpg?pwd08zk 提取码&#xff1a;08zk 二、安装NET Framework 3.5 双击打开控制面板&#xff0c;点击【卸载程序】&…

World of Warcraft [CLASSIC][80][the Ulduar]

Ulduar 奥杜尔副本介绍 奥杜尔共计14个BOSS&#xff0c;通常说的10H就是10个苦难模式就是全通&#xff0c;9H就是除了【观察者奥尔加隆】&#xff0c;特别说明开启【观察者奥尔加隆】&#xff0c;是需要打掉困难模式4个守护者的。 所以人们经常说的类似“10H 观察者”、“10H…

Python开发日记 -- 实现bin文件的签名

目录 1.数据的不同表现形式签名值不一样&#xff1f; 2.Binascii模块简介 3.问题定位 4.问题总结 1.数据的不同表现形式签名值不一样&#xff1f; Happy Muscle试运行了一段时间&#xff0c;组内同事再一次提出了新的需求&#xff1a;需要对bin文件签名。 PS&#xff1a;服…

react18中的函数组件底层渲染原理分析

react 中的函数组件底层渲染原理 react组件没有局部与全局之分&#xff0c;它是一个整体。这点跟vue的组件化是不同的。要实现 react 中的全局组件&#xff0c;可以将组件挂在react上&#xff0c;这样只要引入了react&#xff0c;就可以直接使用该组件。 函数式组件的创建 …

Kafka之消费者客户端

1、历史上的二个版本 与生产者客户端一样&#xff0c;在Kafka的发展过程当中&#xff0c;消费者客户端主要有两个大的版本&#xff1a; 旧消费者客户端&#xff08;Old Consumer&#xff09;&#xff1a;基于Scala语言开发的版本&#xff0c;又称为Scala消费者客户端。新消费…

【力扣】GO解决子序列相关问题

文章目录 一、引言二、动态规划方法论深度提炼子序列问题的通用解法模式 三、通用方法论应用示例&#xff1a;最长递增子序列&#xff08;LeetCode题目300&#xff09;Go 语言代码实现 四、最长连续递增序列&#xff08;LeetCode题目674&#xff09;Go 语言代码实现 五、最长重…

ffmpeg视频滤镜:定向模糊-dblur

滤镜简述 dblur 官网链接 > https://ffmpeg.org/ffmpeg-filters.html#dblur 有一个模糊滤镜&#xff0c;我试了一下&#xff0c;没有感觉到它的特殊之处, 这里简单介绍一下。 滤镜使用 滤镜的参数 angle <float> ..FV.....T. set angle (from 0 t…

找不到包的老版本???scikit-learn,numpy,scipy等等!!

废话不多说 直接上链接了&#xff1a; https://pypi.tuna.tsinghua.edu.cn/simple/https://pypi.tuna.tsinghua.edu.cn/simple/https://pypi.tuna.tsinghua.edu.cn/simple/xxx/ 后面的这个xxx就是包的名字 大家需要什么包的版本&#xff0c;直接输进去就可以啦 举个栗子&#…

零基础Java第十期:类和对象(一)

目录 一、拜访对象村 1.1. 什么是面向对象 1.2. 面向对象与面向过程 二、类定义和使用 2.1. 类的定义格式 2.2. 类的定义练习 三、类的实例化 3.1. 什么是实例化 3.2. 类和对象的说明 四、this引用 4.1. 什么是this引用 4.2. this引用的特性 一、拜访对象村 在…

<项目代码>YOLOv8路面病害识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

STMicroelectronics意法半导体车规芯片系列--亿配芯城(ICgoodFind)

在汽车电子领域&#xff0c;意法半导体的车规级芯片系列一直备受瞩目。亿配芯城作为电子元器件领域的可靠供应商&#xff0c;为大家介绍意法半导体车规级芯片系列的卓越之处。 意法半导体在车规级芯片领域拥有深厚的技术积累和丰富的经验。 其车规级芯片涵盖了多个关键领域&am…

8.three.js相机详解

8.three.js相机详解 1、 认识相机 在Threejs中相机的表示是THREE.Camera&#xff0c;它是相机的抽象基类&#xff0c;其子类有两种相机&#xff0c;分别是正投影相机THREE.OrthographicCamera和透视投影相机THREE.PerspectiveCamera&#xff1a; 正投影和透视投影的区别是&am…

【Java】常用方法合集

以 DemoVo 为实体 import lombok.Data; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;Data ExcelIgnoreUnannotated public class ExportPromoteUnitResult {private String id;ExcelProperty(value &qu…

贪心算法记录 - 下

135. 分发糖果 困难 n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。 你需要按照以下要求&#xff0c;给这些孩子分发糖果&#xff1a; 每个孩子至少分配到 1 个糖果。相邻两个孩子评分更高的孩子会获得更多的糖果。 请你给每个孩子分发糖果&#xff0c…

一文搞懂指令周期,机器周期和时钟周期

如图&#xff1a; 指令周期 > 机器周期 > 时钟周期 指令周期&#xff1a;一个指令&#xff0c;从取值到执行的全部周期。一个指令执行过程包括取值&#xff0c;译码和执行阶段。 机器周期&#xff1a;,取指、间址、执行和中断等 时钟周期&#xff1a;时钟频率的倒数&am…

什么样的JSON编辑器才好用

简介 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人阅读和编写&#xff0c;同时也便于机器解析和生成。随着互联网和应用程序的快速发展&#xff0c;JSON已经成为数据传输和存储的主要格式之一。在处理和编辑JSON数据…