老旧前端项目如何升级工程化的项目
因为历史的原因存在着大量的老旧前端项目,而在今天的开发环境中已经不再适应了,于是产生了升级到新的环境的需求。比如笔者当前的一个登录页面项目,就是以下面为技术栈的老旧项目。
- 基于 jQuery
- 包管理基于 require.js,甚至有的没有使用全局变量
- 没有工程化,只使用 nginx 跑起页面
问题的产生
当前旧工程乃基于 Java 工程下的子前端项目,通过 Java Maven 打包压缩混淆 js/css,如下 Maven 插件的配置。
其实,笔者认为通过 Java 项目整合前端工程,也是一个不错的全栈选择,其特点是前后端虽然各自开发,但在同一工程下,打包部署亦是同时一起进行的,也就是打包部署没有前后端分离,前后端一起打包部署。这一点,是自从 Servlet 3.0 发布之后 JAR 包完全具备整合相关静态资源的能力(以前的 war 包就是一个网站,当然支持静态资源混合,JAR 包则不然)。而且通过 Maven 插件实现前端构建打包,也是一个不错的思路。但通过一系列实践得出的结论是,如果是小型的前端项目,修改不多,则此法甚秒,然则比较大型的前端项目,频繁修改的话,则此法弊端渐显。因为哪怕小小的前端修改,又要跟随后端项目发包部署,时间漫长且心里压力不舒坦。
于是在后来的升级改造中,我们希望可以部署也可以前端分离。
是否改造为 MVVM 架构
jQuery 无疑是以前主流的前端库,如果贸然升级到今天的 vue/react,貌似也没有很大的必要,MVVM 固然很好而且顺手,但升级需要考虑升级后的风险,如果业务代码不通过测试则麻烦很大,抵消了升级后带来的收益。况且,新的 node/npm 工程也完全支持 jQuery 引入,只是配置上稍有不同。总之,最后决定保留 jQuery 旧有的开发模式。
升级到 node/npm 环境
今天主流前端开发离不开 node/npm,于是升级第一件事就是切换此环境。构建工具,显然还是以 webpack 为主。
JS 迁移
Require.js 使用 AMD 语法,Webpack 同样支持 AMD 语法,能够动态分析项目中的依赖项,这是项目迁移成功的关键所在。只不过 Webpack 不是直接支持,而且需要稍微配置一下。
先看一下原项目中的`requirejs.config.js``:
requirejs.config({
baseUrl: '/public/js',
paths: {
jquery: 'lib/jquery',
jqueryUI: 'lib/jquery-ui',
moment: 'lib/moment',
qs: 'lib/qs',
lodash: 'lib/lodash',
selectize: 'lib/selectize',
}
});
require(['jquery', 'moment', 'modal/index'], function( $, moment, modal ){
console.log($);
});
对 require.js 中的配置内容映射为 webpack 中的 alias,以解决路径解析问题。让 webpack 能够解析上面path
中的地址。
开始配置·webpack.config.js·中的resolve.alias
:
const path = require('path');
const resolve = filePath => path.resolve(process.cwd(), filePath);
module.exports = {
resolve: {
alias: {
jquery: resolve('lib/jquery'),
jqueryUI: resolve('lib/jquery-ui'),
moment: resolve('lib/moment'),
qs: resolve('lib/qs'),
lodash: resolve('lib/lodash'),
selectize: resolve('lib/selectize'),
}
},
}
这样可以让 webpack 可以正确解析 require 的依赖。同时 require 的也可以引入不是 require 的包。
Inline js
内联 JS 是指在 html 中通过<script>
置入的脚本。 这类脚本只能复制到新的一个包中,优先加载。
src js
src JS 是指在 html 中通过<scrip src=“xxx.js”>
置入的脚本。 这类 JS 迁移到新的 JS 包,由 index.js/main.js 导入。
JS 压缩、混淆、source map
Webpack 构建下,打包成品是否压缩的取决于webpack.config.js
的mode
,设置为production
即可压缩。
打开 devtool: 'source-map'
生成 source map。
混淆需要 webpack-obfuscator 插件。
参考:
- 用 webpack 替换 requirejs 打包
- From Require.js to Webpack — Part 2 (The How)
引入 CSS
以前是<link rel="stylesheet" type="text/css" href="css/normalize.css" />
方式引入 CSS 的,现在不用,改为在 js 包中:
import '../css/normalize.css';
import '../css/font_login/iconfont.css';
Inline CSS style 不作修改。
图片迁移
Webpack5 新特性:资源模块 ,诸如引入图片的方式可以使用 webpack5 中的资源模块,而 url-loader, file-loader, raw-loader 三个库已经不再维护。
新方式:
module: {
rules: [
// 图片处理
{
test: /\.(png|jpg|svg|jpeg|gif)$/i,
type: "asset/resource",
},
],
},
对于需要转化成 base64 的图片,可以使用type: asset/inline
。
对于其他资源文件,有需要被 XHR(Ajax)请求的资源,也可以如法炮制,关键是让 Webpack 能够识别是可控的资源,然后require()
。例如 jQ 的 i18n 资源。
module: {
rules: [
{// i18n 资源文件
test: /\.(properties)$/i,
type: "asset/resource",
},
],
},
参考:《构建webpack5.x 知识体系:3、基础之图片、html、js、其他配置》、手把手带你学webpack(3)-- Webpack中加载图片及其他资源 、webpack5资源最佳加载方案。
JS 里面的图片
例如这种,在 JS 动态引入图片url('image/index/portal_bg.jpg')
:
通过上面的 asset 配置之后,我们需要使用require()
去引入图片地址。
HTML 里面的图片资源
HTML 里面的图片指的是<img src />
引入的图片。虽然在 webpack5 中我们使用assets-module
资源模块类型(asset module type)来替代比如 raw-loader 、url-loader、file-loader,但 HTML 图片不在assets-module
负责之列。这是因为默认情况下,HTML 模板只是纯文本,Webpack 不能理解你想要复制在文本中引用的 asset。
这个时候需要再次增加一个 loader 配置来处理 HTML。添加 html-loader 依赖:
npm i html-loader -d
修改webpack.config.js
:
module: {
rules: [
{ test: /.html$/, loader: 'html-loader' } // 修正 HTML 加载 img src 图片
],
},
然后修改图片位置,注意是相对于 HTML 文件的位置:
关于 base64 图片
base64 后的图片会增加体积。
不要无脑将所有图片都使用 base64 编码处理,我们使用base64编码的主要目的还是为了减少http请求
对于那些数量多,但是体积小的图片文件,如果以文件的方式打包的话会产生多条 http请求,而如果把它们全都编码成 base64 存在 js 文件中,那 http 请求只会有一次。
图片压缩插件:image-webpack-loader。
开发环境服务器
为了能够有一个良好的开发体验,我们需要在代码更新时及时看到更新后的效果,这时候就需要webpack-dev-server
的帮助了。
devServer: {
static: {
directory: path.resolve(__dirname, '../dist'),
},
watchFiles: ['public/**/*'],
compress: true,
port: 8000,
open: false, // 是否自动打开浏览器
hot: true,
},
hot: true
表示开启 HMR 热更新,open
则表示运行webpack serve
命令后自动启动浏览器打开页面。
proxy 代理配置
旧有的方式是将页面 url 与接口 url 通过本地的 Nginx 配置在同一域(Domian)下面,Nginx 反代接口,接口访问接口。如今在 Webpack 的帮助下,一切都那么自然丝滑,——它整合了一个后台服务,相当于 Ng 角色,就是代理。
devServer: {
proxy: [ // 配置代理(只在本地开发有效,上线无效)
{
context: ['/api'],
target: 'http://xxxx:8100/aut',
changeOrigin: true
}
]
},
代理接口可能要多试几下,在 Postman 等工具中调通再回到开发环境中试试。
参考:1、2。