背景

毋庸置疑,NodeJS全栈开发包括NodeJS在前端的应用,也包括NodeJS在后端的应用。CabloyJS前端采用Vue+Framework7,采用Webpack进行打包。CabloyJS后端是基于EggJS开发的上层框架。我们知道,EggJS采用的是约定优于配置的原则,当服务启动时,会在约定的目录加载controllerservice诸如此类的文件。那么,我们基于EggJS开发的后端代码,是否也可以像前端一样进行Webpack打包呢?

意义

为什么要提出这样一个命题:NodeJS后端编译打包?

因为NodeJS后端编译打包有如下两个显著的好处:

1. 保护商业代码

编译打包,可以将源码进行丑化,满足保护商业代码的需求。虽然丑化javascript代码无法完全避免反编译,但我们要基于一个原则:丑化最主要的目的是保护开发团队的工作量。可以想象,反编译及以反编译为基础的二次开发,工作量并不小

2. 提升启动性能

编译打包,可以将众多散乱的javascript文件合并成一个文件,从而提升后端服务的启动性能。这在大型项目的开发中,效果更加显著

在接下来的案例中,我们会以模块egg-born-module-test-party为例。该模块后端有63个js源码文件,通过编译打包后只生成一个backend.js文件。当后端服务启动时,一个模块只需加载一个文件,性能肯定优于加载63个文件。如果一个大型项目包含100个业务模块,这种性能优势就会更加明显

目标

进行JS文件打包的工具有很多,由于CabloyJS前端是采用Webpack进行打包,因此,在这里,我们也只探讨Webpack在后端的打包方式

前提条件

我们知道,Webpack是从一个入口文件开始,通过检索require方法,得到一棵完整的文件依赖树,然后把这些依赖树合并成一个文件,最后进行丑化

而EggJS采用的是约定优于配置的原则,文件之间的依赖关系是隐性约定的,而不是通过require显式声明的。因此,在这种机制下面,Webpack打包是不起作用的

但是EggJS的定位就是框架的框架,使得我们可以在EggJS的基础之上开发新的框架。CabloyJS后端就是在EggJS的基础之上,进行了进一步的扩展和封装,使得controllerservicemiddlewareconfig等诸如此类的定义文件,可以通过require方法显式声明,从而可以让Webpack提炼出一棵完整的文件依赖树,进而完成编译打包工作

这篇文章的重点,不是要说明CabloyJS后端是如何对EggJS进行的扩展和封装,而是要说明,在已经实现require显式声明的前提条件下,NodeJS后端如何进行编译打包

准备工作

egg-born-module-test-party是CabloyJS的测试模块,包含大量测试用例。我们以该模块为例来说明NodeJS后端编译打包的方方面面

1. 下载模块

我们先将模块源码下载到本地

$ git clone https://github.com/zhennann/egg-born-module-test-party.git

如果没有git命令行工具,可以直接从GitHub官网下载:https://github.com/zhennann/egg-born-module-test-party

2. 安装依赖

$ npm i

3. 编译打包

npm run build:backend

核心概念

只要我们指定了入口文件,Webpack就会自动通过require 检索文件依赖树。因此,剩下的核心工作,就是通过配置文件来调整Webpack的行为

webpack.base.conf.js

文件:/build/backend/webpack.base.conf.js

const path = require('path');
const config = require('./config.js'); const nodeModules = {
require3: 'commonjs2 require3',
}; function resolve(dir) {
return path.join(__dirname, '../../backend', dir);
} module.exports = {
entry: {
backend: resolve('src/main.js'),
},
target: 'node',
output: {
path: config.build.assetsRoot,
filename: '[name].js',
library: 'backend',
libraryTarget: 'commonjs2',
},
externals: nodeModules,
resolve: {
extensions: [ '.js', '.json' ],
},
module: {
rules: [],
},
node: {
console: false,
global: false,
process: false,
__filename: false,
__dirname: false,
Buffer: false,
setImmediate: false,
},
};

1. entry/output

通过entry/output的组合,我们指定了一个入口文件src/main.js,最终编译打包成一个输出文件backend.js

2. target: 'node'

Webpack是一个通用的打包工具,既可以用于前端浏览器,也可以用于后端NodeJS。因此,我们需要指定target为node,从而为后端NodeJS打包。比如,在后端node场景下,一些内置的模块就会被排除在打包之列,如fspath等等

3. node

为了让原本为后端NodeJS开发的代码可以在前端浏览器中运行,Webpack提供了模拟策略。比如,globalprocess__filename__dirname都是NodeJS内置的对象。如果代码中包含了这些对象,而代码又需要在前端运行,就需要进行模拟。我们这里讨论的是后端编译,所以,就直接统一赋值false,从而禁用模拟行为

4. resolve.extensions

如果我们在使用require引用源码文件时没有指定文件扩展名,那么Webpack会通过resolve.extensions帮我们匹配合适的文件名

5. module.rules

Webpack除了可以打包js文件,还可以打包css/image/text等资源文件。因为这里是后端打包,所以,不需要设置module.rules

6. externals

在这里重点要说的是节点externals

在实际的业务开发中,我们难免会用到大量第三方模块,这些模块一般都安装在node_modules目录,比如moment。因为我们也是通过const moment=require('moment')的方式引用第三方库,所以,Webpack也会尝试把moment打包进来

一方面,第三方模块数量众多,如果进行打包,最终输出文件过大。另一方面,对于保护商业代码没有任何意义。所以,我们需要想一个办法把这些第三方模块从打包依赖树中排除掉

- 排除moment

如果我们要排除moment,可以这样配置:

externals: {
moment: 'commonjs2 moment'
}

- 排除node_modules

如果我们要排除node_modules目录下的所有第三方模块,可以这样配置:

var fs = require('fs');

var nodeModules = {};
fs.readdirSync('node_modules')
.filter(function(x) {
return ['.bin'].indexOf(x) === -1;
})
.forEach(function(mod) {
nodeModules[mod] = 'commonjs2 ' + mod;
}); module.exports = {
...
externals: nodeModules
...
}

- 更优雅的策略

针对这种场景,CabloyJS单独开发了一个NPM模块require3https://github.com/zhennann/require3

我们只需要在externals中排除require3这一个模块就可以了。其余的模块都通过require3进行引用,从而轻松避免了被打包的行为

const nodeModules = {
require3: 'commonjs2 require3',
}; module.exports = {
...
externals: nodeModules
...
}

在实际业务代码中,一般这样引用:

const require3 = require('require3');
const moment = require3('moment');

moment通过require3引用,从而避免被Webpack打包

webpack.prod.conf.js

文件:/build/backend/webpack.prod.conf.js

const webpack = require('webpack');
const config = require('./config.js');
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf'); const env = config.build.env; const plugins = [
new webpack.DefinePlugin({
'process.env': env,
}),
]; const webpackConfig = merge(baseWebpackConfig, {
mode: 'production',
devtool: config.build.productionSourceMap ? 'source-map' : false,
plugins,
optimization: {
runtimeChunk: false,
splitChunks: false,
minimize: config.build.uglify,
},
}); module.exports = webpackConfig;

1. mode: 'production'

通过指定mode为production,指示Webpack使用与production相关的内置的优化策略

2. devtool

指示Webpack是否生成source map文件,如果要生成,source map的文件格式是什么

详细的格式清单,请参考:https://webpack.js.org/configuration/devtool/

3. optimization.minimize

由于我们只需输出一个单文件,所以只需通过optimization.minimize指示Webpack是否需要最小化(丑化)即可

===> 杀手锏

经过前面的配置,我们已经可以非常便利的进行后端NodeJS打包了,而且打包后的文件已经进行了丑化。可是,有些网友认为这些工作还不够,希望打包之后的文件可以再乱一些

下面我们就借用babel对js文件做进一步的代码转译工作。先把配置放出来,然后再一一解释

文件:/build/backend/webpack.base.conf.js

  ...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
babelrc: false,
// presets: [ '@babel/preset-env' ],
plugins: [
'@babel/plugin-transform-arrow-functions',
'@babel/plugin-transform-for-of',
'@babel/plugin-transform-parameters',
'@babel/plugin-transform-shorthand-properties',
'@babel/plugin-transform-spread',
'@babel/plugin-transform-template-literals',
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-transform-async-to-generator',
],
},
},
},
],
},
...

1. test

我们仅对后缀名为.js的文件进行babel转译

2. exclude

排除node_modules目录下的js文件

3. use.loader

使用babel-loader对js文件进行转译

4. use.options

babel-loader的转译参数

4.1 babelrc: false

转译参数既可以在options中直接配置,也可以在项目根目录创建一个.babelrc文件,然后在文件中配置。在这里,我们直接在options中配置转译参数

4.2 presets

babel的转译工作都是通过一系列插件的组合来完成的。我们可以把一系列插件的组合定义为preset。@babel/preset-env是babel提供的预配置组合,包含大量的插件。但是这些预配置的插件组合如果都生效的话,会破坏后端NodeJS代码的某些特性,产生不可预期的问题。所以,我们把presets参数注释掉,手工添加我们所需要的插件组合

4.3 plugins

启用太多的babel插件,一方面会影响编译的效率,另一方面,有些babel插件会破坏后端NodeJS代码的某些特性,产生不可预期的问题。经过实际测试,启用以下babel插件即可把后端NodeJS代码转译到惨不忍睹的地步。前面我们也提到一个原则:丑化最主要的目的是保护开发团队的工作量

插件名称 用途
arrow-functions 转译箭头函数
for-of 转译for-of循环
parameters 转译ES2015函数参数
shorthand-properties 转译简写属性
spread 转译...展开形式
template-literals 转译模版字符串
object-rest-spread 转译对象展开表达式
async-to-generator async方法转译为生成器

async/await本质上就是生成器+Promise的语法糖。因此,把async方法转译为生成器,不仅可以显著打乱NodeJS代码的逻辑流,而且也是回归到了本质,反而提升了NodeJS代码的性能

关于Babel插件的更详细信息,请参考:https://babeljs.io/docs/en/plugins

编译打包

最后,让我们再执行一次NodeJS后端的编译打包指令

npm run build:backend

CabloyJS全栈开发之旅(1):NodeJS后端编译打包全攻略的更多相关文章

  1. 巨蟒python全栈开发flask8 MongoDB回顾 前后端分离之H5&pycharm&夜神

    1.MongoDB回顾 .启动 mongod - 改变data/db位置: --dbpath D:\data\db mongod --install 安装windows系统服务 mongod --re ...

  2. CabloyJS带你轻松走进NodeJS全栈开发-免费课程 作者亲授

    课程说明 B站直播 为回馈新老同学对开源框架CabloyJS的支持与厚爱,快速而轻松的开启NodeJS全栈开发之旅.2019年9月5日至9月11日在B站开启了一波免费直播培训课程 课程信息,请点击链接 ...

  3. koa+mysql+vue+socket.io全栈开发之数据访问篇

    后端搭起大体的框架后,接着涉及到的就是如何将数据持久化的问题,也就是对数据库进行 CURD 操作. 关于数据库方案, mongodb 和 mysql 都使用过,但我选用的是 mysql,原因: 目前为 ...

  4. Win10构建Python全栈开发环境With WSL

    目录 Win10构建Python全栈开发环境With WSL 启动WSL 总结 对<Dev on Windows with WSL>的补充 Win10构建Python全栈开发环境With ...

  5. python全栈开发学习_内容目录及链接

    python全栈开发学习_day1_计算机五大组成部分及操作系统 python全栈开发学习_day2_语言种类及变量 python全栈开发_day3_数据类型,输入输出及运算符 python全栈开发_ ...

  6. 转-subl配置全栈开发环境

    为 Sublime Text 3 设置 Python 的全栈开发环境 Sublime Text 3 (ST3) 是一个轻量级的跨平台文字编辑器,尤以其轻快的速度,易用性和强大的社区支持而著称.它一经面 ...

  7. 一文读懂NodeJS全栈开发利器:CabloyJS(万字长文)

    目录 0 修订 0.1 修订说明 0.2 修订历史 1 基本概念 1.1 CabloyJS是什么 1.2 CabloyJS核心解决什么问题 1.3 CabloyJS的开发历程 2 数据版本与开发流程 ...

  8. NodeJS全栈开发利器:CabloyJS究竟是什么

    CabloyJS CabloyJS是一款顶级NodeJS全栈业务开发框架, 基于KoaJS + EggJS + VueJS + Framework7 文档 官网 && 文档 演示 PC ...

  9. Meteor全栈开发平台 - 不仅仅是前端

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,博客地址为http://www.cnblogs.com/jasonnode/ .网站上有对应每一 ...

随机推荐

  1. IO流的简单实现

    IO流的几种实现方式 学习目标: 例题: 字节输出流 字节输入流 字符输入流 字符输出流 学习目标: 熟练掌握IO流的基本实现方式 例题: 字节输出流 代码如下: public class Outpu ...

  2. 帝国CMS批量提取正文内容到简介

    最近接到一个帝国CMS模板改版项目,自带的数据可能是采集的,以前的简介字段内容只截取了60个字,新模板的简介60字符太少了,不美观,想让简介都截取200个字,怎么批量修改呢,文章太多了手动改肯定不行, ...

  3. DevC++ 报错[Error] Id returned 1 exit status

    DevC++ 报错[Error] Id returned 1 exit status 起因 学校机房的计算机总是二次编译总是报错 报错提示 [Error] Id returned 1 exit sta ...

  4. 不借助 Javascript,利用 SVG 快速构建马赛克效果

    之前在公众号转发了好友 Vajoy 的一篇文章 -- 巧用 CSS 把图片马赛克风格化. 核心是利用了 CSS 中一个很有意思的属性 -- image-rendering,它可以用于设置图像缩放算法. ...

  5. javascript中的Ajax基础(一)

    一.手写一个ajax 1 const xhr = new xmlHttpRequest() 2 3 xhr.open(请求方式:post get, 请求地址, 同步或者异步) 4 5 xhr.onre ...

  6. 【Vue3+Express实战】项目开发环境搭建

    大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注 点赞 加我微信:frontendpicker,一起学习交流前端,成为更优秀的工程师-关注公众号:搞前端的半夏,了解更多前端知 ...

  7. Primitive Primes - 题解【数学】

    题面 It is Professor R's last class of his teaching career. Every time Professor R taught a class, he ...

  8. WebSocket 协议详解

    一.WebSocket 协议背景 早期,在网站上推送消息给用户,只能通过轮询的方式或 Comet 技术.轮询就是浏览器每隔几秒钟向服务端发送 HTTP 请求,然后服务端返回消息给客户端. 轮询技术一般 ...

  9. C语言超全学习路线(收藏让你少走弯路)

    刚入门是否觉得C语言很难?那可能是你还没找到正确的C语言学习路线,收藏以防找不到,让你少走弯路. 基本语法 选择控制语句 if,swith 循环控制语句 while,for 控制语句相关关键字分析 变 ...

  10. Python 迭代器、生成器、可迭代对象

    迭代器 1 #迭代器定义: 2 #类中得有__iter__和__next__两个方法 3 #__iter__方法放回对象本身,即:self(是迭代器对象) 4 #__next__方法,返回下一个数据, ...