开源商城系统 深度图像 CK csv ipad canvas configuration Movejs vuejs视频教程 jq绑定click事件 android逆向工程师 hbase端口 python开发安卓应用 css选择器有几种 matlab取实部 flutter 缺点 python算法 python写文件 java入门 java数据库连接 java入门编程 java函数 java字符串查找 java获取当前年 java字符 h5模板 图吧导航怎么样 自制题库答题考试软件 js获取父节点 iar下载 掌门一对一下载 保留小数点后两位 vbs代码 onaccuracychanged cad2008汉化包 vbs代码表白 小米自动开关机 dll下载站 pandas中文手册 vue上传图片
当前位置: 首页 > 学习教程  > 

CabloyJS全栈开发之旅(1):NodeJS后端编译打包全攻略

2020/10/16 17:59:52 文章标签: nodejs后端开发

背景 毋庸置疑,NodeJS全栈开发包括NodeJS在前端的应用,也包括NodeJS在后端的应用😅。CabloyJS前端采用VueFramework7,采用Webpack进行打包。CabloyJS后端是基于EggJS开发的上层框架。我们知道,EggJS采用的是约定优于配…

背景

毋庸置疑,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模块require3: https://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-generatorasync方法转译为生成器

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

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

编译打包

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

npm run build:backend

本文链接: http://www.dtmao.cc/news_show_300252.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?