一、前言

这段时间看了一些sfx的源码,收获颇深。本想找个时间更新一篇文章,但是最近事情比较多,没有时间去整理这些东西。趁这两天闲了下来,便整理了一下,然后跟大家分享一下。

二、SFX介绍

sfx是一款优秀的用于迅速构建Web的应用工具,开发者只需要关注项目逻辑的代码,而不需要关心webpack打包、搭建本地Node服务等等诸如此类的这些问题。是一款基于模板化的开发工具,也就是把已经搭建好的项目结构给照搬过来,所有的配置都是暴露出来的,并且可以根据实际情况去做一些自定义配置,更加灵活自由,并且可以直接接入已有项目,并带来本地开发环境和生产环境的收益。
目前sfx2.x版本主要支持以下几个功能:

  • sfx2 init <projectName> 初始化项目模板文件
  • sfx2 dev [address] 使用sfx内置的node服务搭建本地开发环境,且可传入自定义webpack配置
  • sfx2 build [tasks...] 使用sfx内置的webpack4进行打包构建,且可传入自定义webpack配置
  • sfx2 eslint [files...] 使用sfx内置的eslint进行代码规范校验,且可传入指定目录文件

  • sfx2 unit [files...] 使用sfx内置的karma进行单元测试,且可传入指定目录文件

三、为什么我们要使用统一脚手架

1、可以减少开发人员配置脚手架带来的时间损耗;

2、统一项目结构,方便管理,降低项目交接时带来熟悉项目的时间;

3、方便统一技术栈,预先引入固定的组件库;

4、提高开发人员在多个项目之间的快速切换能力,提高项目可维护性,统一公司技术栈,避免因为环境不同导致奇怪的问题。

四、SFX项目结构

整个项目的目录结构如上图所示,下面我大概介绍每个文件夹的东西大致都是干嘛的。

1、bin (存放sfx入口文件,这里放的sfx的一些命令文件,比如sfx init这样的命令都是从由这里控制的)

2、docs (一些sfx的项目介绍和使用注意事项啥的)

3、eslint(存放sfx进行eslint代码校验的自定义方法)

3、lib (这里存放着一些需要的一些自定义方法)

4、node_modules (第三方node模块 )

5、template(存放sfx进行init模板初始化的自定义方法与项目结构的模板)

6、util(存放封装的工具函数)

7、webpack(存放sfx进行dev开启本地node开发环境,build打包构建生产环境,unit进行单元测试的自定义方法和相关webpack配置文件)

8、一些杂七杂八的东西 (比如eslint配置、.gitignore、LICENSE等等诸如此类这些东西,不影响我们阅读源码,可以直接忽略掉。)

9、package.json/README.md

五、分析源码

1、package.json

按常规来说,了解一个项目,首先看package.json

{    "name": "@sxf/sfx2",   "version": "2.1.5",   "description": "sfx cli",    "main": "index.js",    "license": "MIT",    "files": [        "bin",        "eslint",        "lib",        "template",        "util",        "webpack"    ],    "keywords": ["sfx",        "sfx2"    ],    "bin": {        "sfx2": "bin/sfx2"    },    "homepage": "http://code.sangfor.org/UED/FE-COMMON/sfx",    "bugs": {        "url": "http://code.sangfor.org/UED/FE-COMMON/sfx/issues"    },    "author": {        "email": "43115@sangfor.com",        "name": "zhangyuantao"    },    "scripts": {        "lint": "eslint"    },    "dependencies": {        "@babel/core": "^7.1.2",        "@babel/plugin-transform-reserved-words": "^7.0.0",        "@babel/preset-env": "^7.1.0",        "@cgroup/eslint-plugin-sfchecklist": "http://code.sangfor.org/UED/FE-COMMON/eslint-plugin-sfchecklist/-/archive/v2.0.0/eslint-plugin-sfchecklist-v2.0.0.tar.gz",        "@sxf/eslint-plugin-sfchecklist": "^3.1.3",        "babel-eslint": "^10.0.1",        "babel-loader": "^8.0.4",        "babel-plugin-transform-es3-member-expression-literals": "^6.22.0",        "babel-plugin-transform-es3-property-literals": "^6.22.0",        "babel-plugin-transform-member-expression-literals": "^6.9.4",        "babel-plugin-transform-property-literals": "^6.9.4",       "babel-polyfill": "^6.26.0",        "cache-loader": "^1.2.2",        "chai": "^4.2.0",        "commander": "^2.19.0",        "copy-webpack-plugin": "^4.5.4",        "css-loader": "^1.0.0",        "eslint": "^5.7.0",        "eslint-friendly-formatter": "^4.0.1",        "eslint-loader": "^2.1.1",        "eslint-plugin-html": "^4.0.6",        "eslint-plugin-vue": "^5.0.0-beta.3",        "eventsource-polyfill": "^0.9.6",        "express": "^4.16.4",        "file-loader": "^2.0.0",        "happypack": "^5.0.0",        "html-webpack-plugin": "^4.0.0-beta.5",        "http-proxy": "^1.17.0",        "is-glob": "^4.0.0",        "istanbul": "^0.4.5",        "istanbul-instrumenter-loader": "^3.0.1",        "karma": "^3.1.1",        "karma-chai": "^0.1.0",        "karma-chrome-launcher": "^2.2.0",        "karma-coverage": "^1.1.2",        "karma-coverage-istanbul-reporter": "^2.0.4",        "karma-mocha": "^1.3.0",        "karma-mocha-reporter": "^2.2.5",        "karma-sourcemap-loader": "^0.3.7",        "karma-webpack": "^3.0.5",        "less": "^3.8.1",        "less-loader": "^4.1.0",        "lodash": "^4.17.11",        "log4js": "^3.0.6",        "micromatch": "^3.1.10",        "mini-css-extract-plugin": "^0.4.4",        "mocha": "^5.2.0",        "opn": "^5.4.0",        "ora": "^3.0.0",        "phantomjs-polyfill": "^0.0.2",        "postcss-initial": "^3.0.0",        "postcss-loader": "^3.0.0",        "sass-loader": "^7.1.0",        "scss-loader": "^0.0.1",        "shelljs": "^0.8.2",        "style-loader": "^0.23.1",        "stylus": "^0.54.5",        "stylus-loader": "^3.0.2",        "ts-loader": "^5.2.2",        "typescript": "^3.1.4",        "uglifyjs-webpack-plugin": "^2.0.1",        "underscore.template": "^0.1.7",        "url-loader": "^1.1.2",        "vue-hot-reload-api": "^2.3.1",        "vue-loader": "^15.4.2",        "vue-style-loader": "^4.1.2",        "vue-template-compiler": "^2.5.17",        "webpack": "^4.22.0",        "webpack-dev-middleware": "^3.4.0",        "webpack-hot-middleware": "^2.24.3",        "webpack-merge": "^4.1.4"    },    "devDependencies": {        "docsify": "3.7.2",        "karma-phantomjs-launcher": "^1.0.4"    }}复制代码

从上述代码中,我们可以看到在package.json提供一个映射到本地本地文件名的bin字段,一旦被引入后,npm将软链接这个文件到prefix/bin里面,以便于全局引入,或者在./node_modules/.bin/目录里,也就是说在npm link执行之后,就能在全局使用sfx2这个指令来执行bin/sfx2文件了。

2、bin/sfx2

#!/usr/bin/env node"use strict";let commander = require('commander');
let sfx2 = require('../index');
const PACKAGE_JSON = require('../package.json');console.log(`Welcome to use sfx cli tool (version: ${PACKAGE_JSON.version}), use "sfx2 --help" show more options.`);commander    .version(PACKAGE_JSON.version)    .usage('<command> [options]')    .option('-n, --nolint', 'remove eslint')    .option('-c, --config [file]', 'sfx.config.js file')    .description(`        For Example:            sfx init vue            sfx build --nolint            sfx dev --config=custom.sfx.config.js            sfx eslint    `);commander    .command('init <template>')    .option('-i, --install', 'auto run yarn install')       .option('-b, --build', 'auto run sfx build')    .description('init folder like yeoman')    .action((template, options) => {        sfx2.init(template, {            install: options.install,            build: options.build        });    });commander    .command('build [tasks...]')    .description('build for production. [thirdParts, project]')    .action((tasks) => {        parseOption(commander);        sfx2.build(tasks).catch(err => {            console.error(err);            process.exit(-1);        });    });commander    .command('dev [address]')    .description('run a local server for debug')    .action(address => {        parseOption(commander);        sfx2.dev(address);    });commander    .command('eslint [files...]')    .description('run eslint')    .action((files) => {        let ret = sfx2.eslint(files);        if (ret === false) {            process.exit(-1);        }    });commander    .command('unit [files...]')    .option('-s, --single', 'single run')    .option('-e, --auto-exit', 'auto exit when karma completed')    .description('run unit test task')    .action((files, options) => {        parseOption(commander);        sfx2.unit(files, {            single: options.single,            autoExit: options.autoExit        });    });commander.parse(process.argv);function parseOption (commander) {if (commander.nolint) {        process.env.COMMANDER_OPTION_NOLINT = !!commander.nolint;    }    if (commander.config) {        process.env.COMMANDER_OPTION_SFX_FILE = commander.config;    }
}复制代码

文件第一行代码#!/usr/bin/env node"use strict"是为了告诉系统,指定使用node执行该脚本文件。

在开始阅读上述源码之前,首先我要了解一个工具(commander),它是node.js命令行界面的完整解决方案,可以在通过解析sfx2后面的命令执行不同操作(函数),并且可以给对应命令设置参数、参数的简写和命令描述等等。

在了解commander的使用方法之后,就可以知道脚手架到底是如何执行的了。

3、template/init

  • 流程图
  • 依赖模块
  • run函数(每个命令都有一个对应的run函数作为该命令的入口函数)
  • 首先执行的是checkEnv方法:
  • 从注释可以看出该方法是用来检测当前目录是否为空,使用node的fs.readdirSync同步获取一个包含“指定目录下所有文件名称”的数组对象,再通过process.cwd获取Node.js 进程的当前工作目录。如果当前目录下不为空,则给出提示并退出进程process.exit(1)。
  • 执行完checkEnv方法之后,进入异步控制流程,执行stdIn函数
  • 上述代码中的prompt方法是通过process.stdout和process.stdin实现询问框的输出和输入
  • 通过用户的输入项目的基本信息之后,返回项目基本配置信息,根据配置信息中的模板名称,开始拷贝template目录下的模板目录文件
  • src为sfx项目中初始化的模板项目文件路径
  • dist为当前sfx运行在哪个工作目录,也就是我们要将模板文件copy到的指定路径
  • option为之前用户填入的项目信息

执行_copy方法

  • existsSync:以同步的方法检测目录是否存在
  • statSync(src).isDirectory: 以同步的方式判断当前路径是否是一个目录
  • mkdirSync:以同步的方式创建目录
  • readdirSync:  返回一个包含“指定目录下所有文件名称”的数组对象
  • writeFileSync:  以同步的方式将数据写入文件,文件已存在的情况下,原内容将被替换

这个方法先后做以下几件事情:

  • 判断模板源目录是否存在,不存在则给对应信息,并退出当前进程,此处应将return修改为process.exit(1),否则后续的run主函数中的代码依旧会执行。
  • 使用递归来遍历源目录来实现整个源目录拷贝到目标路径

4、webpack/dev/index (后续更新)

5、webpack/third_part/index  (后续更新)

6、webpack/prod/index (后续更新)

7、eslint/lint (后续更新)

8、webpack/test/index (后续更新)

六、对比其他脚手架

对比omi-cli,sfx可以直接接入已有的项目来给项目带来开发环境和生产环境的收益,而omi-cli则是注重的是为开发者更快的进入一个新项目的开发。

七、可优化的部分

1、在webpack.config.base.js中的多进程压缩配置UglifyJsPlugin中的parallel选项设置为true默认开启require('os').cpus().length - 1个并发数来进行压缩,可将其设置为require('os').cpus().length来增加一个并发数来提高压缩的效率。

2、sfx init的流程可以做优化

  • 这个流程减少了用户自己去创建文件夹并进入文件夹的操作
  • 在指定模板的时候应该根据已有的模板去给用户选择,如下图:

转载于:https://juejin.im/post/5cfd1b525188252ee729656f

浅析SFX脚手架源码相关推荐

  1. Spring Cloud脚手架源码

    Spring Cloud脚手架源码 @(SpringCloud)[Spring Cloud,框架,组成] Spring Cloud脚手架源码 基本介绍 思维导图 源码 基本介绍 Spring Clou ...

  2. springboot 定时器_基于SpringCloud?+?SpringBoot的 SaaS型微服务脚手架源码分享

    简介: 基于SpringCloud(Hoxton.SR3) + SpringBoot(2.2.6.RELEASE) 的 SaaS型微服务脚手架,具备用户管理.资源权限管理.网关统一鉴权.Xss防跨站攻 ...

  3. @mpx/cli 脚手架源码解析

    前言 Mpx是一款致力于提高小程序开发体验的增强型小程序框架,通过Mpx,我们能够以最先进的web开发体验(Vue + Webpack)来开发生产性能深度优化的小程序. 下面说说mpx脚手架的源码: ...

  4. 初识洋葱模型,分析中间件执行过程,浅析koa中间件源码

    前言 作为洋葱模型的第一篇文章,这里仅介绍了一些入门级知识,比如 了解洋葱模型执行顺序 分析部分 koa 中间件的源码来加深对中间件的认识 为第二篇文章:分析洋葱模型实现原理,在自己项目中接入洋葱模型 ...

  5. Eclipse Theia技术揭秘——脚手架源码分析

    在之前的文章中,我们介绍了Theia的构建,其中用到了很多theia的命令,这些命令来自于@theia/cli这个库,本篇文章我们就对@theia/cli以及相关联的库进行分析.本篇文章是继构建桌面I ...

  6. Javaweb中提到的反射浅析(附源码)

    反射:一个jdk5.0的新特性,高级运用.在后期的框架中,这个是一大重点,现在估计我们都不会太多的接触他的.但是为了后面的铺垫,我想还是先了解一下: 先构造一个类,然后我们用反射来获取,调用里面的方法 ...

  7. mpx脚手架mpx-template模板源码解析

    前言 mpx脚手架中使用的模板为mpx-template,里面做了一些配置化的东西,如果了解源码后,可以自定义模板和脚手架. git地址(2019年12月19日版本):https://github.c ...

  8. React Native Autolinking 源码深入分析

    目录 关于link autolink 源码分析 1. native_modules.gradle 2.getReactNativeConfig 2.1.getReactNativeConfig 2.2 ...

  9. vue 分模块打包 脚手架_Vue面试官最爱的底层源码问题,你可以这样回答!

    最近看到身边很多人都在投简历,有因为企业裁员的,有因为自己想跳槽的,原因不一,但是最终大家都会需要接触到面试这个事情.但是很多人对待面试不够认真,只会等待结果,不去努力.所以这边想整理一些懒人面试技巧 ...

最新文章

  1. 辞职之后的思考--激励
  2. 002.iSCSI服务端配置
  3. Maven最佳实践:版本管理
  4. 01ts简介和相关配置
  5. 数据埋点方案和规范确定
  6. MultiSlider组件
  7. 音视频技术下一个风口在哪里——LiveVideoStackCon 音视频技术大会 2022 上海站演讲剧透...
  8. 机器学习中为什么需要梯度下降_梯度下降直觉 - 机器是如何学习的
  9. 如何找tensorflow-gpu版本对应的cuda和cudnn
  10. canoco5冗余分析步骤_打造高性能的大数据分析平台
  11. ELMo代码详解(一):数据准备
  12. do...while循环
  13. ESP分区重建,解决各种引导问题
  14. json 生成 json字符串
  15. 如何通过官方原版win10PE安装纯净版win10系统
  16. 如何用深度学习模型为自己做个漫画画像(含代码流程)
  17. 文治者必有武备不然长大了挨欺负_有文事者必有武备,有武备者必有文事。
  18. c语言输入名字判断姓是否缩写,C语言复习笔记
  19. fprintf()函数的运用
  20. ASP.NET MVC 音乐商店 - 8. 使用 Ajax 更新的购物车

热门文章

  1. 一个好用的android图片压缩工具类
  2. 华为java面试题目
  3. 对于有些网站无法打开F12或者firebug的现象解答
  4. 〖Python自动化办公篇⑭〗- Excel 文件自动化 - 写入图表
  5. (45)Air Band OpenCV2.4.13_输入视频和相似性度量
  6. 36_ue4[UI]05_3DUI显示与展示
  7. 学编程一年多少中学_我从一年的编码中学到了什么
  8. 安卓镜像刻录软件_电脑运行安卓镜像 电脑引导安卓 安卓镜像
  9. 打印服务Print Spooler开启后自动关闭
  10. linux 卸载nexus,CentOS7安装Nexus