技术标签: umi umi-build-dev bin 命令行工具 【源码解读】
以
umi build
为例,查看umi命令行工具的逻辑
首先查看package.json文件的bin
字段,找到umi可执行文件的位置:
"bin": {
"umi": "./bin/umi.js"
},
查看umi/bin/umi.js
文件,实际逻辑是在umi/src/cli.js
文件中,执行umi build
// umi/src/cli.js
switch (script) {
case 'build':
case 'dev':
case 'test':
case 'inspect':
case 'ui':
// eslint-disable-next-line import/no-dynamic-require
// build进来 require('./scripts/build')
require(`./scripts/${
script}`);
break;
default: {
const Service = require('umi-build-dev/lib/Service').default;
new Service(buildDevOpts(args)).run(aliasMap[script] || script, args);
break;
}
}
build命令会动态导入umi/src/scripts/build.js
import yParser from 'yargs-parser';
import buildDevOpts from '../buildDevOpts';
process.env.NODE_ENV = 'production';
process.env.UMI_UI = 'none';
// 设置两个环境变量
const args = yParser(process.argv.slice(2));
const Service = require('umi-build-dev/lib/Service').default;
new Service(buildDevOpts(args)).run('build', args);
// 生成一个service实例,整个打包的逻辑都在里面(包括注册各个插件,生成webpack配置,webpack打包),然后运行实例的run函数
先看生成实例的参数是buildDevOpts(args)
,buildDevOpts
来自umi/src/buildDevOpts.js
:
export default function(opts = {
}) {
loadDotEnv(); // 解析.env,.env.local文件里的环境变量到process.env中
let cwd = opts.cwd || process.env.APP_ROOT || process.cwd();
if (cwd) {
if (!isAbsolute(cwd)) {
cwd = join(process.cwd(), cwd);
}
cwd = winPath(cwd);
// 原因:webpack 的 include 规则得是 \ 才能判断出是绝对路径
if (isWindows()) {
cwd = cwd.replace(/\//g, '\\');
}
}
// 返回项目的目录路径,上面也有针对windows路径作不同处理
return {
cwd,
};
}
function loadDotEnv() {
const baseEnvPath = join(process.cwd(), '.env');
const localEnvPath = `${
baseEnvPath}.local`;
const loadEnv = envPath => {
if (existsSync(envPath)) {
const parsed = parse(readFileSync(envPath, 'utf-8'));
Object.keys(parsed).forEach(key => {
// eslint-disable-next-line no-prototype-builtins
if (!process.env.hasOwnProperty(key)) {
process.env[key] = parsed[key];
}
});
}
};
loadEnv(baseEnvPath);
loadEnv(localEnvPath);
}
umi-build-dev/src/Service.js
首先看下Service的构造函数:
constructor({
cwd }) {
// 用户传入的 cmd 不可信任 转化一下
this.cwd = cwd || process.cwd();
try {
// 首先将项目的package.json文件放到实例的`pkg`变量中
this.pkg = require(join(this.cwd, 'package.json')); // eslint-disable-line
} catch (e) {
this.pkg = {
};
}
// babel编译所有的配置文件,防止有不兼容的语法
registerBabel({
cwd: this.cwd,
});
this.commands = {
}; // 存放build,dev等命令函数,具体函数来自内置插件,umi-build-dev/src/plugins/commands
this.pluginHooks = {
}; // 存放打包时的一些钩子函数
this.pluginMethods = {
};
this.generators = {
}; // 存放umi g的模板生成器,生成页面文件等
this.UmiError = UmiError;
this.printUmiError = printUmiError;
// 解析用户配置
this.config = UserConfig.getConfig({
cwd: this.cwd,
service: this,
});
debug(`user config: ${
JSON.stringify(this.config)}`);
// 解析插件
this.plugins = this.resolvePlugins();
this.extraPlugins = [];
debug(`plugins: ${
this.plugins.map(p => p.id).join(' | ')}`);
// 存储相关文件的具体路径
this.paths = getPaths(this);
}
看下registerBabel
函数:
// umi-build-dev/src/registerBabel.js
import {
join, isAbsolute } from 'path';
import {
existsSync } from 'fs';
import registerBabel from 'af-webpack/registerBabel';
import {
winPath } from 'umi-utils';
import {
getConfigPaths } from 'umi-core/lib/getUserConfig';
import {
uniq } from 'lodash';
let files = [];
function initFiles(cwd) {
files = uniq(files.concat(getConfigPaths(cwd)));
}
export function addBabelRegisterFiles(extraFiles, {
cwd }) {
initFiles(cwd);
files = uniq(files.concat(extraFiles));
}
export default function({
cwd }) {
initFiles(cwd); // 获取所有的配置文件路径,存到files变量中
const only = files.map(f => {
const fullPath = isAbsolute(f) ? f : join(cwd, f);
return winPath(fullPath);
});
let absSrcPath = join(cwd, 'src');
if (!existsSync(absSrcPath)) {
absSrcPath = cwd;
}
// 运行`af-webpack/registerBabel`函数转义所有的配置文件,防止配置文件中有不兼容的语法。
registerBabel({
// only suport glob
// ref: https://babeljs.io/docs/en/next/babel-core.html#configitem-type
only,
babelPreset: [
require.resolve('babel-preset-umi'),
{
env: {
targets: {
node: 8 } },
transformRuntime: false,
},
],
babelPlugins: [
[
require.resolve('babel-plugin-module-resolver'),
{
alias: {
'@': absSrcPath,
},
},
],
],
});
}
af-webpack/registerBabel.js中使用@babel/register
编译:
export default function registerBabel(opts = {
}) {
const {
only, ignore, babelPreset, babelPlugins, disablePreventTest } = opts;
if (disablePreventTest || process.env.NODE_ENV !== 'test') {
require('@babel/register')({
presets: [require.resolve('@babel/preset-typescript'), babelPreset],
plugins: babelPlugins || [],
only,
ignore,
extensions: ['.es6', '.es', '.jsx', '.js', '.mjs', '.ts', '.tsx'],
babelrc: false,
cache: false,
});
}
}
配置文件有这些,umi文档中也有讲:
export function getConfigPaths(cwd): string[] {
const env = process.env.UMI_ENV;
return [
join(cwd, 'config/'),
join(cwd, '.umirc.js'),
join(cwd, '.umirc.ts'),
join(cwd, '.umirc.local.js'),
join(cwd, '.umirc.local.ts'),
...(env ? [join(cwd, `.umirc.${
env}.js`), join(cwd, `.umirc.${
env}.ts`)] : []),
];
}
再看解析插件resolvePlugins
,umi-build-dev/src/getPlugins.js
:
export default function(opts = {
}) {
const {
cwd, plugins = [] } = opts;
// 内置插件
const builtInPlugins = [
'./plugins/commands/dev',
'./plugins/commands/build',
'./plugins/commands/inspect',
'./plugins/commands/test',
'./plugins/commands/help',
'./plugins/commands/generate',
'./plugins/commands/rm',
'./plugins/commands/config',
...(process.env.UMI_UI === 'none' ? [] : [require.resolve('umi-plugin-ui')]),
'./plugins/commands/block',
'./plugins/commands/version',
'./plugins/global-js',
'./plugins/global-css',
'./plugins/mountElementId',
'./plugins/mock',
'./plugins/proxy',
'./plugins/history',
'./plugins/afwebpack-config',
'./plugins/404', // 404 must after mock
'./plugins/targets',
'./plugins/importFromUmi',
];
// 将内置插件和用户给的插件合并后返回
const pluginsObj = [
// builtIn 的在最前面
...builtInPlugins.map(p => {
let opts;
if (Array.isArray(p)) {
/* eslint-disable prefer-destructuring */
opts = p[1];
p = p[0];
/* eslint-enable prefer-destructuring */
}
const apply = require(p); // eslint-disable-line
return {
id: p.replace(/^.\//, 'built-in:'),
apply: apply.default || apply, // apply方法执行对应的内置插件
opts,
};
}),
...getUserPlugins(process.env.UMI_PLUGINS ? process.env.UMI_PLUGINS.split(',') : [], {
cwd }),
...getUserPlugins(plugins, {
cwd }),
];
debug(`plugins: \n${
pluginsObj.map(p => ` ${
p.id}`).join('\n')}`);
return pluginsObj;
}
构造函数完毕后,就会执行service.run
函数:
run(name = 'help', args) {
this.init();
return this.runCommand(name, args);
}
name是命令的名称,此时是build
,可以看到默认会执行umi help
命令
this.init()
:
init() {
// 加载环境变量
this.loadEnv();
// 初始化插件 这里很重要,会填充this.commands、this.pluginHooks、this.pluginMethods等对象,方便调用
this.initPlugins();
// 重新加载用户配置
const userConfig = new UserConfig(this);
const config = userConfig.getConfig({
force: true });
mergeConfig(this.config, config);
this.userConfig = userConfig;
if (config.browserslist) {
deprecate('config.browserslist', 'use config.targets instead');
}
debug('got user config');
debug(this.config);
// assign user's outputPath config to paths object
if (config.outputPath) {
const {
paths } = this;
paths.outputPath = config.outputPath;
paths.absOutputPath = join(paths.cwd, config.outputPath);
}
debug('got paths');
debug(this.paths);
}
this.initPlugins
只需要看最重要的部分:
plugins.forEach(plugin => {
// 每个插件都调用initPlugin
this.initPlugin(plugin);
this.plugins.push(plugin);
// reset count
count = 0;
initExtraPlugins();
});
this.initPlugin
:
initPlugin(plugin) {
const {
id, apply, opts } = plugin;
try {
assert(
typeof apply === 'function',
`
plugin must export a function, e.g.
export default function(api) {
// Implement functions via api
}
`.trim(),
);
// 生成PluginAPI实例,包含插件会使用到的一些公用方法
const api = new Proxy(new PluginAPI(id, this), {
get: (target, prop) => {
if (this.pluginMethods[prop]) {
return this.pluginMethods[prop];
}
if (
[
// methods
'changePluginOption',
'applyPlugins',
'_applyPluginsAsync',
'writeTmpFile',
'getRoutes',
'getRouteComponents',
// properties
'cwd',
'config',
'webpackConfig',
'pkg',
'paths',
'routes',
// error handler
'UmiError',
'printUmiError',
// dev methods
'restart',
'printError',
'printWarn',
'refreshBrowser',
'rebuildTmpFiles',
'rebuildHTML',
].includes(prop)
) {
if (typeof this[prop] === 'function') {
return this[prop].bind(this);
} else {
return this[prop];
}
} else {
return target[prop];
}
},
});
api.onOptionChange = fn => {
assert(
typeof fn === 'function',
`The first argument for api.onOptionChange should be function in ${
id}.`,
);
plugin.onOptionChange = fn;
};
// 执行插件函数
apply(api, opts);
plugin._api = api;
} catch (e) {
if (process.env.UMI_TEST) {
throw new Error(e);
} else {
signale.error(
`
Plugin ${
chalk.cyan.underline(id)} initialize failed
${
getCodeFrame(e, {
cwd: this.cwd })}
`.trim(),
);
debug(e);
process.exit(1);
}
}
}
initPlugin的最后会执行apply函数,以内置插件为例,apply就是运行内置插件的函数(上面有提到
),查看build内置插件:
// umi-build-dev/src/plugins/commands/build/index.js
export default function(api) {
// api是pluginAPI类的实例
const {
service, debug, config, UmiError, printUmiError } = api;
const {
cwd, paths } = service;
// 调用pluginApi实例的registerCommand方法,将build命令需要执行的函数方法放置到service.commands中,也就是下面的第三个参数
api.registerCommand(
'build',
{
webpack: true,
description: 'building for production',
},
args => {
const watch = args.w || args.watch;
notify.onBuildStart({
name: 'umi', version: 2 });
const RoutesManager = getRouteManager(service);
RoutesManager.fetchRoutes();
return new Promise((resolve, reject) => {
process.env.NODE_ENV = 'production';
service.applyPlugins('onStart');
service._applyPluginsAsync('onStartAsync').then(() => {
service.rebuildTmpFiles = () => {
filesGenerator.rebuild();
};
service.rebuildHTML = () => {
service.applyPlugins('onHTMLRebuild');
};
const filesGenerator = getFilesGenerator(service, {
RoutesManager,
mountElementId: config.mountElementId,
});
filesGenerator.generate();
function startWatch() {
filesGenerator.watch();
service.userConfig.setConfig(service.config);
service.userConfig.watchWithDevServer();
}
if (process.env.HTML !== 'none') {
const HtmlGeneratorPlugin = require('../getHtmlGeneratorPlugin').default(service);
// move html-webpack-plugin to the head, so that
// other plugins (like workbox-webpack-plugin)
// which listen to `emit` event can detect assets
service.webpackConfig.plugins.unshift(new HtmlGeneratorPlugin());
}
service._applyPluginsAsync('beforeBuildCompileAsync').then(() => {
require('af-webpack/build').default({
cwd,
watch,
// before: service.webpackConfig
// now: [ service.webpackConfig, ... ] , for ssr or more configs
webpackConfig: [
service.webpackConfig,
...(service.ssrWebpackConfig ? [service.ssrWebpackConfig] : []),
],
// stats now is Array MultiStats
// [ clientStats, ...otherStats ]
onSuccess({
stats }) {
debug('Build success');
if (watch) {
startWatch();
}
if (process.env.RM_TMPDIR !== 'none' && !watch) {
debug(`Clean tmp dir ${
service.paths.tmpDirPath}`);
rimraf.sync(paths.absTmpDirPath);
}
if (service.ssrWebpackConfig) {
// replace using manifest
// __UMI_SERVER__.js/css => umi.${hash}.js/css
const clientStat = Array.isArray(stats.stats) ? stats.stats[0] : stats;
if (clientStat) {
replaceChunkMaps(service, clientStat);
}
}
service.applyPlugins('onBuildSuccess', {
args: {
stats,
},
});
service
._applyPluginsAsync('onBuildSuccessAsync', {
args: {
stats,
},
})
.then(() => {
debug('Build success end');
notify.onBuildComplete({
name: 'umi', version: 2 }, {
err: null });
resolve();
});
},
// stats now is Array MultiStats
// [ clientStats, ...otherStats ]
onFail({
err, stats }) {
service.applyPlugins('onBuildFail', {
args: {
err,
stats,
},
});
notify.onBuildComplete({
name: 'umi', version: 2 }, {
err });
printUmiError(
new UmiError({
message: err && err.message,
context: {
err,
stats,
},
}),
{
detailsOnly: true },
);
reject(err);
},
});
});
});
});
},
);
}
上面的api.registerCommand最后实际是调用的service的registerCommand方法:
registerCommand(name, opts, fn) {
if (typeof opts === 'function') {
fn = opts;
opts = null;
}
opts = opts || {
};
assert(!(name in this.commands), `Command ${
name} exists, please select another one.`);
// 以build内置插件为例,将build需要执行的函数和参数放置到this.commands中
this.commands[name] = {
fn, opts };
}
然后执行build命令,this.runCommand(name, args)
:
runCommand(rawName, rawArgs = {
}, remoteLog) {
debug(`raw command name: ${
rawName}, args: ${
JSON.stringify(rawArgs)}`);
const {
name, args } = this.applyPlugins('_modifyCommand', {
initialValue: {
name: rawName,
args: rawArgs,
},
});
debug(`run ${
name} with args ${
JSON.stringify(args)}`);
const command = this.commands[name];
if (!command) {
signale.error(`Command ${
chalk.underline.cyan(name)} does not exists`);
process.exit(1);
}
const {
fn, opts } = command; // 取出build命令的执行函数和参数
if (opts.webpack) {
// opts.webpack也是在调用api.registerCommand时配置的
// webpack config 获取webpack配置,打包时会用到
this.webpackConfig = require('./getWebpackConfig').default(this, {
watch: rawArgs.w || rawArgs.watch,
});
if (this.config.ssr) {
// when use ssr, push client-manifest plugin into client webpackConfig
this.webpackConfig.plugins.push(
new (require('./plugins/commands/getChunkMapPlugin').default(this))(),
);
// server webpack config
this.ssrWebpackConfig = require('./getWebpackConfig').default(this, {
ssr: this.config.ssr,
});
}
}
// 执行
return fn(args, {
remoteLog,
});
}
}
可以看到最后运行的build命令就是存放到this.commands
中的,来看看build命令的执行函数:
// umi-build-dev/src/plugins/commands/build/index.js中api.registerCommand的第三个参数
args => {
const watch = args.w || args.watch;
notify.onBuildStart({
name: 'umi', version: 2 });
const RoutesManager = getRouteManager(service);
RoutesManager.fetchRoutes();
return new Promise((resolve, reject) => {
process.env.NODE_ENV = 'production';
// appluPlugins执行的是service.pluginHooks里的钩子函数,这也是在生产pluginAPI实例时初始化的
service.applyPlugins('onStart');
service._applyPluginsAsync('onStartAsync').then(() => {
service.rebuildTmpFiles = () => {
filesGenerator.rebuild();
};
service.rebuildHTML = () => {
service.applyPlugins('onHTMLRebuild');
};
const filesGenerator = getFilesGenerator(service, {
RoutesManager,
mountElementId: config.mountElementId,
});
filesGenerator.generate();
function startWatch() {
filesGenerator.watch();
service.userConfig.setConfig(service.config);
service.userConfig.watchWithDevServer();
}
if (process.env.HTML !== 'none') {
const HtmlGeneratorPlugin = require('../getHtmlGeneratorPlugin').default(service);
// move html-webpack-plugin to the head, so that
// other plugins (like workbox-webpack-plugin)
// which listen to `emit` event can detect assets
service.webpackConfig.plugins.unshift(new HtmlGeneratorPlugin());
}
service._applyPluginsAsync('beforeBuildCompileAsync').then(() => {
// 最关键内容,调用af-webpack/build进行打包
require('af-webpack/build').default({
cwd,
watch,
// before: service.webpackConfig
// now: [ service.webpackConfig, ... ] , for ssr or more configs
webpackConfig: [
service.webpackConfig,
...(service.ssrWebpackConfig ? [service.ssrWebpackConfig] : []),
],
// stats now is Array MultiStats
// [ clientStats, ...otherStats ]
onSuccess({
stats }) {
debug('Build success');
if (watch) {
startWatch();
}
if (process.env.RM_TMPDIR !== 'none' && !watch) {
debug(`Clean tmp dir ${
service.paths.tmpDirPath}`);
rimraf.sync(paths.absTmpDirPath);
}
if (service.ssrWebpackConfig) {
// replace using manifest
// __UMI_SERVER__.js/css => umi.${hash}.js/css
const clientStat = Array.isArray(stats.stats) ? stats.stats[0] : stats;
if (clientStat) {
replaceChunkMaps(service, clientStat);
}
}
service.applyPlugins('onBuildSuccess', {
args: {
stats,
},
});
service
._applyPluginsAsync('onBuildSuccessAsync', {
args: {
stats,
},
})
.then(() => {
debug('Build success end');
notify.onBuildComplete({
name: 'umi', version: 2 }, {
err: null });
resolve();
});
},
// stats now is Array MultiStats
// [ clientStats, ...otherStats ]
onFail({
err, stats }) {
service.applyPlugins('onBuildFail', {
args: {
err,
stats,
},
});
notify.onBuildComplete({
name: 'umi', version: 2 }, {
err });
printUmiError(
new UmiError({
message: err && err.message,
context: {
err,
stats,
},
}),
{
detailsOnly: true },
);
reject(err);
},
});
});
});
});
}
其中最重要的就是调用af-webpack/build
进行webpack打包,并且传入对应的webpack配置,在上面的代码42行
。
整个打包过程的核心就是注册多个插件,而我们平时执行的build
dev
g
等命令都是位于umi-build-dev/src/plugins/commands/
目录下的内置插件。
文章浏览阅读2.1k次。python画生肖兔_python画兔子代码
文章浏览阅读1.5k次。电脑店超级U盘装系统-设置U盘启动(V5.0 UD+ISO二合一版 )2012-11-30 21:02 点击进入电脑店超级U盘装系统-程序下载和运行(V5.0 UD+ISO二合一版 ) 点击进入电脑店超级U盘装系统-个性化设置和升级安装(V5.0 UD+ISO二合一版 ) 点击进入电脑店超级U盘装系统-设置U盘启动(V5.0 UD_电脑店u盘装系统有捆绑吗
文章浏览阅读4.7k次,点赞9次,收藏25次。同一物体同时开启NavMeshAgent和Rigidbody时,经常会发生一些意想不到的受力问题,经实测,NavMeshAgent在自动导航时,并不是直接改变物体的位移,会赋予物体一定的速度。关于组件:参考:Rigidbody、CharacterController和NavMeshAgent的区别Rigidbody是用来模拟真实物理效果的,它可以设置重力,可以为对象施加外力。注意它和..._navmeshagent不能和物理效果同时生效吗
文章浏览阅读275次。临近国庆,又回过头来鼓捣docker,因为从事php开发,所以还是先从环境入手。本来考虑搭建php+mysql+nginx+redis全部,但是由于使用的都是公司的mysql和redis,故只搭建php+nginx,因为我的操作系统是win10,一下操作都是在win下完成的。首先先拉取镜像,当然你也可以自己编写dockerfile去构建自己的镜像。这里先拉取nginx镜像:docker pu..._dockerfile 安装php+nginx
文章浏览阅读1.2k次,点赞41次,收藏16次。计算机毕业设计吊打导师Hadoop+Hive+PySpark旅游景点推荐 旅游推荐系统 景区游客满意度预测与优化 Apriori算法 景区客流量预测 旅游大数据 景点规划 知识图谱 机器学习 深度学习 人工智能
文章浏览阅读1.2k次。阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android本篇文章主要介绍开发中我们没有源码的GMS Crash崩溃后的解决方案,通过阅读本篇文章,您将收获以下内容:一、gms.ui Service not registered CrashGMS(GoogleMobile Service)包是出口国外手机中Google限制必须要预制的,如果不预置无法过Google CTS认证,会导致手...
文章浏览阅读2.7w次,点赞211次,收藏852次。某高校欲开发一个成绩管理系统,记录并管理所有选修课程的学生的平时成绩和考试成绩,其主要功能描述如下: 1. 每门课程都有3到6个单元构成,每个单元结束后会进行一次测试,其成绩作为这门课程的平时成绩。课程结束后进行期末考试,其成绩作为这门课程的考试成绩。 2. 学生的平时成绩和考试成绩均由每门课程的主讲教师上传给成绩管理系统。 3. 在记录学生成绩之前,系统需要验证这些成绩是否有效。首先..._招聘考试成绩管理数据流程图
文章浏览阅读1.9k次。算术操作符:无论是在sqlserver,或者是java中,每种语言它都有算术操作符,大同小异。Oracle中算术操作符(+)(-)(*)(/)值得注意的是:/在oracle中就相当于显示中的除法5/2= 2.5比较操作符:其中等号可以换成其他运算符:(后面为该操作符的单条件查询样例)!=不等于selectempno,ename,jobfromscott.empwherejob!..._oracl除后剩余数
文章浏览阅读2.8k次。BodyIBM i安全邮件配置和常见故障排除方法简介:电子邮件是现在普遍使用的一种通信方式,为了提高通信过程中的安全并且保护邮件内容不被泄露,IBM i SMTP增加了对TLS的支持, 通过此技术保障了邮件通信过程中的安全和数据的不被篡改。本文提供了IBM i V7R2及以上版本安全邮件的配置方法和常见故障排除方法。术语缩写:SMTP:Simple Mail Transfer ProtocolTL..._邮件dcm
文章浏览阅读402次。创建VLAN的指令:VLAN +ID号(取值范围1-4094,1是默认存在)用来连接终端设备的(pc,打印机,监控摄像头)片面认为,access接口下面接的一定是终端。交换网络本身的工作原理是什么?交换机是基于MAC地址表转发的。hybrid(华为特有)_2019vlan
文章浏览阅读4.8k次,点赞3次,收藏44次。静态手势识别总体方案0.说明1.实现目标2.实现步骤1)总体思路2)每部分效果基于高斯肤色模型和动态阈值的手势分割基于Canny算法的轮廓提取基于Hu矩的量化基于傅里叶描述子的量化分类融合特征分类其他尝试0.说明静态手势识别是2019年四五月份做的一次设计,实验平台是Matlab。主要针对静态手势,采用肤色模型分离手部区域,提取手势的轮廓信息,采用不同的描述方式进行量化,最后采用BP神经网络和..._手势识别有哪些方案
文章浏览阅读1.4w次。##机器学习(Machine Learning)&深度学习(Deep Learning)资料(Chapter 1)---#####注:机器学习资料[篇目一](https://github.com/ty4z2008/Qix/blob/master/dl.md)共500条,[篇目二](https://github.com/ty4z2008/Qix/blob/master/dl2.md)开始更新...