mirror of
https://gitee.com/WeBank/fes.js.git
synced 2024-11-29 10:17:45 +08:00
feat(重写构建): 重写构建
This commit is contained in:
parent
a49f328088
commit
28498a30bc
@ -1,6 +1,7 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
lib
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
|
@ -3,7 +3,7 @@ import { join } from 'path';
|
||||
|
||||
// utils must build before core
|
||||
// runtime must build before renderer-react
|
||||
const headPkgs = ['fes-runtime', 'fes-core'];
|
||||
const headPkgs = ['fes-runtime', 'fes-core', 'fes', 'fes-plugin-built-in'];
|
||||
const tailPkgs = [];
|
||||
// const otherPkgs = readdirSync(join(__dirname, 'packages')).filter(
|
||||
// (pkg) =>
|
||||
|
@ -196,8 +196,6 @@ export default class Config {
|
||||
return {};
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
addAffix(file, affix) {
|
||||
const ext = extname(file);
|
||||
return file.replace(new RegExp(`${ext}$`), `.${affix}${ext}`);
|
||||
|
@ -2,7 +2,7 @@ import assert from 'assert';
|
||||
import * as utils from '@umijs/utils';
|
||||
import { isValidPlugin, pathToObj } from './utils/pluginUtils';
|
||||
import { EnableBy, PluginType, ServiceStage } from './enums';
|
||||
|
||||
import Logger from '../logger/logger';
|
||||
// TODO
|
||||
// 标准化 logger
|
||||
export default class PluginAPI {
|
||||
@ -11,6 +11,7 @@ export default class PluginAPI {
|
||||
this.key = opts.key;
|
||||
this.service = opts.service;
|
||||
this.utils = utils;
|
||||
this.logger = new Logger(`fes:plugin:${this.id || this.key}`);
|
||||
}
|
||||
|
||||
// TODO: reversed keys
|
||||
|
@ -180,18 +180,7 @@ export default class Service extends EventEmitter {
|
||||
// 1. merge default config
|
||||
// 2. validate
|
||||
this.setStage(ServiceStage.getConfig);
|
||||
const defaultConfig = await this.applyPlugins({
|
||||
key: 'modifyDefaultConfig',
|
||||
type: this.ApplyPluginsType.modify,
|
||||
initialValue: await this.configInstance.getDefaultConfig()
|
||||
});
|
||||
this.config = await this.applyPlugins({
|
||||
key: 'modifyConfig',
|
||||
type: this.ApplyPluginsType.modify,
|
||||
initialValue: this.configInstance.getConfig({
|
||||
defaultConfig
|
||||
})
|
||||
});
|
||||
await this.setConfig();
|
||||
|
||||
// merge paths to keep the this.paths ref
|
||||
this.setStage(ServiceStage.getPaths);
|
||||
@ -209,6 +198,21 @@ export default class Service extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
async setConfig() {
|
||||
const defaultConfig = await this.applyPlugins({
|
||||
key: 'modifyDefaultConfig',
|
||||
type: this.ApplyPluginsType.modify,
|
||||
initialValue: await this.configInstance.getDefaultConfig()
|
||||
});
|
||||
this.config = await this.applyPlugins({
|
||||
key: 'modifyConfig',
|
||||
type: this.ApplyPluginsType.modify,
|
||||
initialValue: this.configInstance.getConfig({
|
||||
defaultConfig
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async initPlugins() {
|
||||
this._extraPlugins = [];
|
||||
this.setStage(ServiceStage.initPlugins);
|
||||
@ -257,7 +261,8 @@ export default class Service extends EventEmitter {
|
||||
'config',
|
||||
'env',
|
||||
'args',
|
||||
'hasPlugins'
|
||||
'hasPlugins',
|
||||
'setConfig'
|
||||
].includes(prop)
|
||||
) {
|
||||
return typeof this[prop] === 'function'
|
||||
@ -422,13 +427,6 @@ export default class Service extends EventEmitter {
|
||||
this.args = args;
|
||||
await this.init();
|
||||
|
||||
// TODO 临时实现
|
||||
await this.applyPlugins({
|
||||
key: 'onGenerateFiles',
|
||||
type: ApplyPluginsType.event
|
||||
});
|
||||
|
||||
|
||||
this.setStage(ServiceStage.run);
|
||||
await this.applyPlugins({
|
||||
key: 'onStart',
|
||||
@ -437,11 +435,11 @@ export default class Service extends EventEmitter {
|
||||
args
|
||||
}
|
||||
});
|
||||
// TODO 执行命令
|
||||
// return this.runCommand({
|
||||
// name,
|
||||
// args
|
||||
// });
|
||||
|
||||
return this.runCommand({
|
||||
name,
|
||||
args
|
||||
});
|
||||
}
|
||||
|
||||
async runCommand({
|
||||
|
@ -73,7 +73,7 @@ export function pathToObj({ path, cwd }) {
|
||||
} else {
|
||||
id = winPath(path);
|
||||
}
|
||||
id = id.replace('@webank/fes-core/lib/plugins', '@@');
|
||||
id = id.replace('@webank/fes-plugin-built-in/lib/plugins', '@@');
|
||||
id = id.replace(/\.js$/, '');
|
||||
|
||||
const key = isPkgPlugin
|
||||
|
@ -3,9 +3,9 @@
|
||||
import Config from './Config/Config';
|
||||
import Service from './Service/Service';
|
||||
import PluginAPI from './Service/PluginAPI';
|
||||
import Logger from './logger/logger';
|
||||
import { PluginType } from './Service/enums';
|
||||
import { isPlugin } from './Service/utils/pluginUtils';
|
||||
import ServiceWithBuiltIn from './ServiceWithBuiltIn';
|
||||
|
||||
export * from './route';
|
||||
|
||||
@ -15,5 +15,5 @@ export {
|
||||
PluginAPI,
|
||||
isPlugin,
|
||||
PluginType,
|
||||
ServiceWithBuiltIn
|
||||
Logger
|
||||
};
|
||||
|
75
packages/fes-core/src/logger/logger.js
Normal file
75
packages/fes-core/src/logger/logger.js
Normal file
@ -0,0 +1,75 @@
|
||||
import {
|
||||
createDebug,
|
||||
chalk
|
||||
} from '@umijs/utils';
|
||||
|
||||
export default class Logger {
|
||||
LOG = chalk.black.bgBlue('LOG');
|
||||
|
||||
INFO = chalk.black.bgBlue('INFO');
|
||||
|
||||
WARN = chalk.black.bgHex('#faad14')('WARN');
|
||||
|
||||
ERROR = chalk.black.bgRed('ERROR');
|
||||
|
||||
PROFILE = chalk.black.bgCyan('PROFILE');
|
||||
|
||||
constructor(namespace) {
|
||||
// TODO: get namespace filename accounding caller function
|
||||
if (!namespace) {
|
||||
throw new Error('logger needs namespace');
|
||||
}
|
||||
this.namespace = namespace;
|
||||
this.profilers = {};
|
||||
this.debug = createDebug(this.namespace);
|
||||
}
|
||||
|
||||
log(...args) {
|
||||
// TODO: node env production
|
||||
console.log(this.LOG, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link logger.info} function is an alias for {@link logger.log()}.
|
||||
* @param args
|
||||
*/
|
||||
info(...args) {
|
||||
console.log(this.INFO, ...args);
|
||||
}
|
||||
|
||||
error(...args) {
|
||||
console.error(this.ERROR, ...args);
|
||||
}
|
||||
|
||||
warn(...args) {
|
||||
console.warn(this.WARN, ...args);
|
||||
}
|
||||
|
||||
formatTiming(timing) {
|
||||
return timing < 60 * 1000
|
||||
? `${Math.round(timing / 10) / 100}s`
|
||||
: `${Math.round(timing / 600) / 100}m`;
|
||||
}
|
||||
|
||||
profile(id, message) {
|
||||
const time = Date.now();
|
||||
const namespace = `${this.namespace}:${id}`;
|
||||
// for test
|
||||
let msg;
|
||||
if (this.profilers[id]) {
|
||||
const timeEnd = this.profilers[id];
|
||||
delete this.profilers[id];
|
||||
process.stderr.write(`${this.PROFILE} `);
|
||||
msg = `${this.PROFILE} ${chalk.cyan(
|
||||
`└ ${namespace}`,
|
||||
)} Completed in ${this.formatTiming(time - timeEnd)}`;
|
||||
console.log(msg);
|
||||
} else {
|
||||
msg = `${this.PROFILE} ${chalk.cyan(`┌ ${namespace}`)} ${message || ''}`;
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
this.profilers[id] = time;
|
||||
return msg;
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
// TODO 拆成独立的包作为内置插件包
|
||||
|
||||
export default [
|
||||
// register methods
|
||||
require.resolve('./registerMethods'),
|
||||
|
||||
// misc
|
||||
require.resolve('./routes'),
|
||||
|
||||
// generate files
|
||||
require.resolve('./generateFiles/core/plugin'),
|
||||
require.resolve('./generateFiles/core/routes'),
|
||||
require.resolve('./generateFiles/core/fesExports'),
|
||||
require.resolve('./generateFiles/fes')
|
||||
];
|
3
packages/fes-plugin-built-in/.fatherrc.js
Normal file
3
packages/fes-plugin-built-in/.fatherrc.js
Normal file
@ -0,0 +1,3 @@
|
||||
export default {
|
||||
disableTypeCheck: false,
|
||||
};
|
36
packages/fes-plugin-built-in/package.json
Normal file
36
packages/fes-plugin-built-in/package.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@webank/fes-plugin-built-in",
|
||||
"version": "2.0.0",
|
||||
"description": "@webank/fes-plugin-built-in",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": ""
|
||||
},
|
||||
"keywords": [
|
||||
"fes"
|
||||
],
|
||||
"authors": [
|
||||
""
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": "",
|
||||
"homepage": "",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@umijs/utils": "^3.2.24",
|
||||
"@umijs/bundler-webpack": "^3.2.23",
|
||||
"@umijs/server": "^3.2.23",
|
||||
"@vue/babel-plugin-jsx": "^1.0.0-rc.3",
|
||||
"@webank/fes-core": "^2.0.0",
|
||||
"cliui": "6.0.0",
|
||||
"vue-loader": "^16.0.0-rc.1",
|
||||
"html-webpack-plugin": "^3.2.0"
|
||||
}
|
||||
}
|
53
packages/fes-plugin-built-in/src/index.js
Normal file
53
packages/fes-plugin-built-in/src/index.js
Normal file
@ -0,0 +1,53 @@
|
||||
// TODO 拆成独立的包作为内置插件包
|
||||
|
||||
export default [
|
||||
// register methods
|
||||
require.resolve('./plugins/registerMethods'),
|
||||
|
||||
// misc
|
||||
require.resolve('./plugins/routes'),
|
||||
|
||||
// generate files
|
||||
require.resolve('./plugins/generateFiles/core/plugin'),
|
||||
require.resolve('./plugins/generateFiles/core/routes'),
|
||||
require.resolve('./plugins/generateFiles/core/fesExports'),
|
||||
require.resolve('./plugins/generateFiles/fes'),
|
||||
|
||||
// bundle configs
|
||||
require.resolve('./plugins/features/alias'),
|
||||
require.resolve('./plugins/features/analyze'),
|
||||
require.resolve('./plugins/features/autoprefixer'),
|
||||
require.resolve('./plugins/features/base'),
|
||||
require.resolve('./plugins/features/chainWebpack'),
|
||||
require.resolve('./plugins/features/chunks'),
|
||||
require.resolve('./plugins/features/cssLoader'),
|
||||
require.resolve('./plugins/features/cssnano'),
|
||||
require.resolve('./plugins/features/copy'),
|
||||
require.resolve('./plugins/features/define'),
|
||||
require.resolve('./plugins/features/devServer'),
|
||||
require.resolve('./plugins/features/devtool'),
|
||||
require.resolve('./plugins/features/externals'),
|
||||
require.resolve('./plugins/features/extraBabelPlugins'),
|
||||
require.resolve('./plugins/features/extraBabelPresets'),
|
||||
require.resolve('./plugins/features/extraPostCSSPlugins'),
|
||||
require.resolve('./plugins/features/hash'),
|
||||
require.resolve('./plugins/features/html'),
|
||||
require.resolve('./plugins/features/inlineLimit'),
|
||||
require.resolve('./plugins/features/lessLoader'),
|
||||
require.resolve('./plugins/features/mountElementId'),
|
||||
require.resolve('./plugins/features/nodeModulesTransform'),
|
||||
require.resolve('./plugins/features/outputPath'),
|
||||
require.resolve('./plugins/features/plugins'),
|
||||
require.resolve('./plugins/features/postcssLoader'),
|
||||
require.resolve('./plugins/features/proxy'),
|
||||
require.resolve('./plugins/features/publicPath'),
|
||||
require.resolve('./plugins/features/styleLoader'),
|
||||
require.resolve('./plugins/features/targets'),
|
||||
require.resolve('./plugins/features/terserOptions'),
|
||||
require.resolve('./plugins/features/theme'),
|
||||
require.resolve('./plugins/features/vueLoader'),
|
||||
|
||||
// commands
|
||||
require.resolve('./plugins/commands/build/build'),
|
||||
require.resolve('./plugins/commands/dev/dev')
|
||||
];
|
@ -0,0 +1,73 @@
|
||||
import { relative } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
import { Logger } from '@webank/fes-core';
|
||||
import {
|
||||
cleanTmpPathExceptCache,
|
||||
getBundleAndConfigs,
|
||||
printFileSizes
|
||||
} from '../buildDevUtils';
|
||||
import generateFiles from '../generateFiles';
|
||||
|
||||
const logger = new Logger('fes:plugin-built-in');
|
||||
|
||||
export default function (api) {
|
||||
const {
|
||||
paths,
|
||||
utils: { rimraf }
|
||||
} = api;
|
||||
|
||||
api.registerCommand({
|
||||
name: 'build',
|
||||
description: 'build application for production',
|
||||
async fn() {
|
||||
cleanTmpPathExceptCache({
|
||||
absTmpPath: paths.absTmpPath
|
||||
});
|
||||
|
||||
// generate files
|
||||
await generateFiles({ api, watch: false });
|
||||
|
||||
// build
|
||||
const {
|
||||
bundler,
|
||||
bundleConfigs,
|
||||
bundleImplementor
|
||||
} = await getBundleAndConfigs({ api });
|
||||
try {
|
||||
// clear output path before exec build
|
||||
if (process.env.CLEAR_OUTPUT !== 'none') {
|
||||
if (paths.absOutputPath && existsSync(paths.absOutputPath || '')) {
|
||||
logger.debug(`Clear OutputPath: ${paths.absNodeModulesPath}`);
|
||||
rimraf.sync(paths.absOutputPath);
|
||||
}
|
||||
}
|
||||
|
||||
const { stats } = await bundler.build({
|
||||
bundleConfigs,
|
||||
bundleImplementor
|
||||
});
|
||||
if (process.env.RM_TMPDIR !== 'none') {
|
||||
rimraf.sync(paths.absTmpPath);
|
||||
}
|
||||
printFileSizes(stats, relative(process.cwd(), paths.absOutputPath));
|
||||
await api.applyPlugins({
|
||||
key: 'onBuildComplete',
|
||||
type: api.ApplyPluginsType.event,
|
||||
args: {
|
||||
stats
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
await api.applyPlugins({
|
||||
key: 'onBuildComplete',
|
||||
type: api.ApplyPluginsType.event,
|
||||
args: {
|
||||
err
|
||||
}
|
||||
});
|
||||
// throw build error
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,232 @@
|
||||
import { Bundler as DefaultBundler } from '@umijs/bundler-webpack';
|
||||
import { join, resolve } from 'path';
|
||||
import { existsSync, readdirSync, readFileSync } from 'fs';
|
||||
import { rimraf, chalk } from '@umijs/utils';
|
||||
import zlib from 'zlib';
|
||||
|
||||
|
||||
export async function getBundleAndConfigs({
|
||||
api,
|
||||
port
|
||||
}) {
|
||||
// bundler
|
||||
const Bundler = await api.applyPlugins({
|
||||
type: api.ApplyPluginsType.modify,
|
||||
key: 'modifyBundler',
|
||||
initialValue: DefaultBundler
|
||||
});
|
||||
|
||||
const bundleImplementor = await api.applyPlugins({
|
||||
key: 'modifyBundleImplementor',
|
||||
type: api.ApplyPluginsType.modify,
|
||||
initialValue: undefined
|
||||
});
|
||||
|
||||
const bundler = new Bundler({
|
||||
cwd: api.cwd,
|
||||
config: api.config
|
||||
});
|
||||
const bundlerArgs = {
|
||||
env: api.env,
|
||||
bundler: { id: Bundler.id, version: Bundler.version }
|
||||
};
|
||||
// get config
|
||||
async function getConfig({ type }) {
|
||||
const env = api.env === 'production' ? 'production' : 'development';
|
||||
const getConfigOpts = await api.applyPlugins({
|
||||
type: api.ApplyPluginsType.modify,
|
||||
key: 'modifyBundleConfigOpts',
|
||||
initialValue: {
|
||||
env,
|
||||
type,
|
||||
port,
|
||||
hot: process.env.HMR !== 'none',
|
||||
entry: {
|
||||
umi: join(api.paths.absTmpPath, 'fes.js')
|
||||
},
|
||||
// @ts-ignore
|
||||
bundleImplementor,
|
||||
async modifyBabelOpts(opts) {
|
||||
return api.applyPlugins({
|
||||
type: api.ApplyPluginsType.modify,
|
||||
key: 'modifyBabelOpts',
|
||||
initialValue: opts
|
||||
});
|
||||
},
|
||||
async modifyBabelPresetOpts(opts) {
|
||||
return api.applyPlugins({
|
||||
type: api.ApplyPluginsType.modify,
|
||||
key: 'modifyBabelPresetOpts',
|
||||
initialValue: opts
|
||||
});
|
||||
},
|
||||
async chainWebpack(webpackConfig, opts) {
|
||||
return api.applyPlugins({
|
||||
type: api.ApplyPluginsType.modify,
|
||||
key: 'chainWebpack',
|
||||
initialValue: webpackConfig,
|
||||
args: {
|
||||
...opts
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
args: {
|
||||
...bundlerArgs,
|
||||
type
|
||||
}
|
||||
});
|
||||
return api.applyPlugins({
|
||||
type: api.ApplyPluginsType.modify,
|
||||
key: 'modifyBundleConfig',
|
||||
initialValue: await bundler.getConfig(getConfigOpts),
|
||||
args: {
|
||||
...bundlerArgs,
|
||||
type
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const bundleConfigs = await api.applyPlugins({
|
||||
type: api.ApplyPluginsType.modify,
|
||||
key: 'modifyBundleConfigs',
|
||||
initialValue: [await getConfig({ type: 'csr' })].filter(
|
||||
Boolean,
|
||||
),
|
||||
args: {
|
||||
...bundlerArgs,
|
||||
getConfig
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
bundleImplementor,
|
||||
bundler,
|
||||
bundleConfigs
|
||||
};
|
||||
}
|
||||
|
||||
export function cleanTmpPathExceptCache({
|
||||
absTmpPath
|
||||
}) {
|
||||
if (!existsSync(absTmpPath)) return;
|
||||
readdirSync(absTmpPath).forEach((file) => {
|
||||
if (file === '.cache') return;
|
||||
rimraf.sync(join(absTmpPath, file));
|
||||
});
|
||||
}
|
||||
|
||||
// These sizes are pretty large. We'll warn for bundles exceeding them.
|
||||
const WARN_AFTER_BUNDLE_GZIP_SIZE = 1.8 * 1024 * 1024;
|
||||
const WARN_AFTER_CHUNK_GZIP_SIZE = 1 * 1024 * 1024;
|
||||
|
||||
export function printFileSizes(stats, dir) {
|
||||
const ui = require('cliui')({ width: 80 });
|
||||
const json = stats.toJson({
|
||||
hash: false,
|
||||
modules: false,
|
||||
chunks: false
|
||||
});
|
||||
|
||||
const filesize = (bytes) => {
|
||||
bytes = Math.abs(bytes);
|
||||
const radix = 1024;
|
||||
const unit = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
let loop = 0;
|
||||
|
||||
// calculate
|
||||
while (bytes >= radix) {
|
||||
bytes /= radix;
|
||||
++loop;
|
||||
}
|
||||
return `${bytes.toFixed(1)} ${unit[loop]}`;
|
||||
};
|
||||
|
||||
const assets = json.assets
|
||||
? json.assets
|
||||
: json?.children?.reduce((acc, child) => acc.concat(child?.assets), []);
|
||||
|
||||
const seenNames = new Map();
|
||||
const isJS = val => /\.js$/.test(val);
|
||||
const isCSS = val => /\.css$/.test(val);
|
||||
|
||||
const orderedAssets = assets.map((a) => {
|
||||
a.name = a.name.split('?')[0];
|
||||
// These sizes are pretty large
|
||||
const isMainBundle = a.name.indexOf('fes.') === 0;
|
||||
const maxRecommendedSize = isMainBundle
|
||||
? WARN_AFTER_BUNDLE_GZIP_SIZE
|
||||
: WARN_AFTER_CHUNK_GZIP_SIZE;
|
||||
const isLarge = maxRecommendedSize && a.size > maxRecommendedSize;
|
||||
return {
|
||||
...a,
|
||||
suggested: isLarge && isJS(a.name)
|
||||
};
|
||||
})
|
||||
.filter((a) => {
|
||||
if (seenNames.has(a.name)) {
|
||||
return false;
|
||||
}
|
||||
seenNames.set(a.name, true);
|
||||
return isJS(a.name) || isCSS(a.name);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (isJS(a.name) && isCSS(b.name)) return -1;
|
||||
if (isCSS(a.name) && isJS(b.name)) return 1;
|
||||
return b.size - a.size;
|
||||
});
|
||||
|
||||
function getGzippedSize(asset) {
|
||||
const filepath = resolve(join(dir, asset.name));
|
||||
if (existsSync(filepath)) {
|
||||
const buffer = readFileSync(filepath);
|
||||
return filesize(zlib.gzipSync(buffer).length);
|
||||
}
|
||||
return filesize(0);
|
||||
}
|
||||
|
||||
function makeRow(a, b, c) {
|
||||
return ` ${a}\t ${b}\t ${c}`;
|
||||
}
|
||||
|
||||
ui.div(
|
||||
`${makeRow(
|
||||
chalk.cyan.bold('File'),
|
||||
chalk.cyan.bold('Size'),
|
||||
chalk.cyan.bold('Gzipped'),
|
||||
)
|
||||
}\n\n${
|
||||
// eslint-disable-next-line
|
||||
orderedAssets.map(asset => makeRow(/js$/.test(asset.name) ? (asset.suggested ? chalk.yellow(join(dir, asset.name)) : chalk.green(join(dir, asset.name))) : chalk.blue(join(dir, asset.name)),
|
||||
filesize(asset.size),
|
||||
getGzippedSize(asset),))
|
||||
.join('\n')}`,
|
||||
);
|
||||
|
||||
|
||||
console.log(
|
||||
`${ui.toString()}\n\n ${chalk.gray(
|
||||
'Images and other types of assets omitted.',
|
||||
)}\n`,
|
||||
);
|
||||
|
||||
if (orderedAssets?.some(asset => asset.suggested)) {
|
||||
// We'll warn for bundles exceeding them.
|
||||
// TODO: use umi docs
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.yellow('The bundle size is significantly larger than recommended.'),
|
||||
);
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Consider reducing it with code splitting: https://umijs.org/docs/load-on-demand',
|
||||
),
|
||||
);
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'You can also analyze the project dependencies using ANALYZE=1',
|
||||
),
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
}
|
243
packages/fes-plugin-built-in/src/plugins/commands/dev/dev.js
Normal file
243
packages/fes-plugin-built-in/src/plugins/commands/dev/dev.js
Normal file
@ -0,0 +1,243 @@
|
||||
import { Server } from '@umijs/server';
|
||||
import {
|
||||
delay
|
||||
} from '@umijs/utils';
|
||||
import assert from 'assert';
|
||||
import {
|
||||
cleanTmpPathExceptCache,
|
||||
getBundleAndConfigs
|
||||
} from '../buildDevUtils';
|
||||
import generateFiles from '../generateFiles';
|
||||
import {
|
||||
watchPkg
|
||||
} from './watchPkg';
|
||||
|
||||
export default (api) => {
|
||||
const {
|
||||
env,
|
||||
paths,
|
||||
utils: {
|
||||
chalk,
|
||||
portfinder
|
||||
}
|
||||
} = api;
|
||||
|
||||
const unwatchs = [];
|
||||
let port;
|
||||
let hostname;
|
||||
let server;
|
||||
|
||||
function destroy() {
|
||||
for (const unwatch of unwatchs) {
|
||||
unwatch();
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
server?.listeningApp?.close();
|
||||
}
|
||||
|
||||
api.registerCommand({
|
||||
name: 'dev',
|
||||
description: 'start a dev server for development',
|
||||
async fn({ args = {} }) {
|
||||
const defaultPort = process.env.PORT || args.port || api.config.devServer?.port;
|
||||
port = await portfinder.getPortPromise({
|
||||
port: defaultPort ? parseInt(String(defaultPort), 10) : 8000
|
||||
});
|
||||
hostname = process.env.HOST || api.config.devServer?.host || '0.0.0.0';
|
||||
console.log(chalk.cyan('Starting the development server...'));
|
||||
process.send({
|
||||
type: 'UPDATE_PORT',
|
||||
port
|
||||
});
|
||||
|
||||
// enable https, HTTP/2 by default when using --https
|
||||
const isHTTPS = process.env.HTTPS || args.https;
|
||||
|
||||
cleanTmpPathExceptCache({
|
||||
absTmpPath: paths.absTmpPath
|
||||
});
|
||||
const watch = process.env.WATCH !== 'none';
|
||||
|
||||
// generate files
|
||||
const unwatchGenerateFiles = await generateFiles({
|
||||
api,
|
||||
watch
|
||||
});
|
||||
if (unwatchGenerateFiles) unwatchs.push(unwatchGenerateFiles);
|
||||
|
||||
if (watch) {
|
||||
// watch pkg changes
|
||||
const unwatchPkg = watchPkg({
|
||||
cwd: api.cwd,
|
||||
onChange() {
|
||||
console.log();
|
||||
api.logger.info('Plugins in package.json changed.');
|
||||
api.restartServer();
|
||||
}
|
||||
});
|
||||
unwatchs.push(unwatchPkg);
|
||||
|
||||
// watch config change
|
||||
const unwatchConfig = api.service.configInstance.watch({
|
||||
userConfig: api.service.userConfig,
|
||||
onChange: async ({
|
||||
pluginChanged,
|
||||
valueChanged
|
||||
}) => {
|
||||
if (pluginChanged.length) {
|
||||
console.log();
|
||||
api.logger.info(
|
||||
`Plugins of ${pluginChanged
|
||||
.map(p => p.key)
|
||||
.join(', ')} changed.`,
|
||||
);
|
||||
api.restartServer();
|
||||
}
|
||||
if (valueChanged.length) {
|
||||
let reload = false;
|
||||
let regenerateTmpFiles = false;
|
||||
const fns = [];
|
||||
const reloadConfigs = [];
|
||||
valueChanged.forEach(({
|
||||
key,
|
||||
pluginId
|
||||
}) => {
|
||||
const {
|
||||
onChange
|
||||
} = api.service.plugins[pluginId].config || {};
|
||||
if (onChange === api.ConfigChangeType.regenerateTmpFiles) {
|
||||
regenerateTmpFiles = true;
|
||||
}
|
||||
if (!onChange || onChange === api.ConfigChangeType.reload) {
|
||||
reload = true;
|
||||
reloadConfigs.push(key);
|
||||
}
|
||||
if (typeof onChange === 'function') {
|
||||
fns.push(onChange);
|
||||
}
|
||||
});
|
||||
|
||||
if (reload) {
|
||||
console.log();
|
||||
api.logger.info(`Config ${reloadConfigs.join(', ')} changed.`);
|
||||
api.restartServer();
|
||||
} else {
|
||||
api.service.userConfig = api.service.configInstance.getUserConfig();
|
||||
|
||||
await api.setConfig();
|
||||
|
||||
if (regenerateTmpFiles) {
|
||||
await generateFiles({
|
||||
api
|
||||
});
|
||||
} else {
|
||||
fns.forEach(fn => fn());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
unwatchs.push(unwatchConfig);
|
||||
}
|
||||
|
||||
// delay dev server 启动,避免重复 compile
|
||||
// https://github.com/webpack/watchpack/issues/25
|
||||
// https://github.com/yessky/webpack-mild-compile
|
||||
await delay(500);
|
||||
|
||||
// dev
|
||||
const {
|
||||
bundler,
|
||||
bundleConfigs,
|
||||
bundleImplementor
|
||||
} = await getBundleAndConfigs({
|
||||
api,
|
||||
port
|
||||
});
|
||||
const opts = bundler.setupDevServerOpts({
|
||||
bundleConfigs,
|
||||
bundleImplementor
|
||||
});
|
||||
|
||||
const beforeMiddlewares = await api.applyPlugins({
|
||||
key: 'addBeforeMiddewares',
|
||||
type: api.ApplyPluginsType.add,
|
||||
initialValue: [],
|
||||
args: {}
|
||||
});
|
||||
const middlewares = await api.applyPlugins({
|
||||
key: 'addMiddewares',
|
||||
type: api.ApplyPluginsType.add,
|
||||
initialValue: [],
|
||||
args: {}
|
||||
});
|
||||
|
||||
server = new Server({
|
||||
...opts,
|
||||
compress: true,
|
||||
https: !!isHTTPS,
|
||||
headers: {
|
||||
'access-control-allow-origin': '*'
|
||||
},
|
||||
proxy: api.config.proxy,
|
||||
beforeMiddlewares,
|
||||
afterMiddlewares: [
|
||||
...middlewares
|
||||
],
|
||||
...(api.config.devServer || {})
|
||||
});
|
||||
const listenRet = await server.listen({
|
||||
port,
|
||||
hostname
|
||||
});
|
||||
return {
|
||||
...listenRet,
|
||||
destroy
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
api.registerMethod({
|
||||
name: 'getPort',
|
||||
fn() {
|
||||
assert(
|
||||
env === 'development',
|
||||
'api.getPort() is only valid in development.',
|
||||
);
|
||||
return port;
|
||||
}
|
||||
});
|
||||
|
||||
api.registerMethod({
|
||||
name: 'getHostname',
|
||||
fn() {
|
||||
assert(
|
||||
env === 'development',
|
||||
'api.getHostname() is only valid in development.',
|
||||
);
|
||||
return hostname;
|
||||
}
|
||||
});
|
||||
|
||||
api.registerMethod({
|
||||
name: 'getServer',
|
||||
fn() {
|
||||
assert(
|
||||
env === 'development',
|
||||
'api.getServer() is only valid in development.',
|
||||
);
|
||||
return server;
|
||||
}
|
||||
});
|
||||
|
||||
api.registerMethod({
|
||||
name: 'restartServer',
|
||||
fn() {
|
||||
console.log(chalk.gray('Try to restart dev server...'));
|
||||
destroy();
|
||||
process.send({
|
||||
type: 'RESTART'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
import { join } from 'path';
|
||||
import { chokidar, winPath, lodash } from '@umijs/utils';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { isPlugin, PluginType } from '@webank/fes-core';
|
||||
|
||||
function getFesPlugins(opts) {
|
||||
return Object.keys({
|
||||
...opts.pkg.dependencies,
|
||||
...opts.pkg.devDependencies
|
||||
}).filter(name => (
|
||||
isPlugin(PluginType.plugin, name)
|
||||
));
|
||||
}
|
||||
|
||||
function getFesPluginsFromPkgPath(opts) {
|
||||
let pkg = {};
|
||||
if (existsSync(opts.pkgPath)) {
|
||||
try {
|
||||
pkg = JSON.parse(readFileSync(opts.pkgPath, 'utf-8'));
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return getFesPlugins({ pkg });
|
||||
}
|
||||
|
||||
export function watchPkg(opts) {
|
||||
const pkgPath = join(opts.cwd, 'package.json');
|
||||
const plugins = getFesPluginsFromPkgPath({ pkgPath });
|
||||
const watcher = chokidar.watch(pkgPath, {
|
||||
ignoreInitial: true
|
||||
});
|
||||
watcher.on('all', () => {
|
||||
const newPlugins = getFesPluginsFromPkgPath({ pkgPath });
|
||||
if (!lodash.isEqual(plugins, newPlugins)) {
|
||||
// 已经重启了,只处理一次就够了
|
||||
opts.onChange();
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
watcher.close();
|
||||
};
|
||||
}
|
||||
|
||||
export function watchPkgs(opts) {
|
||||
const unwatchs = [watchPkg({ cwd: opts.cwd, onChange: opts.onChange })];
|
||||
if (winPath(opts.cwd) !== winPath(process.cwd())) {
|
||||
unwatchs.push(watchPkg({ cwd: process.cwd(), onChange: opts.onChange }));
|
||||
}
|
||||
return () => {
|
||||
unwatchs.forEach((unwatch) => {
|
||||
unwatch();
|
||||
});
|
||||
};
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
import { chokidar, lodash, winPath } from '@umijs/utils';
|
||||
import { join } from 'path';
|
||||
|
||||
export default async ({ api, watch }) => {
|
||||
const { paths } = api;
|
||||
|
||||
async function generate() {
|
||||
api.logger.debug('generate files');
|
||||
await api.applyPlugins({
|
||||
key: 'onGenerateFiles',
|
||||
type: api.ApplyPluginsType.event
|
||||
});
|
||||
}
|
||||
|
||||
const watchers = [];
|
||||
|
||||
await generate();
|
||||
|
||||
function unwatch() {
|
||||
watchers.forEach((watcher) => {
|
||||
watcher.close();
|
||||
});
|
||||
}
|
||||
|
||||
function createWatcher(path) {
|
||||
const watcher = chokidar.watch(path, {
|
||||
// ignore .dotfiles and _mock.js
|
||||
ignored: /(^|[/\\])(_mock.js$|\..)/,
|
||||
ignoreInitial: true
|
||||
});
|
||||
watcher.on(
|
||||
'all',
|
||||
lodash.throttle(async () => {
|
||||
await generate();
|
||||
}, 100),
|
||||
);
|
||||
}
|
||||
|
||||
if (watch) {
|
||||
const watcherPaths = await api.applyPlugins({
|
||||
key: 'addTmpGenerateWatcherPaths',
|
||||
type: api.ApplyPluginsType.add,
|
||||
initialValue: [
|
||||
paths.absPagesPath,
|
||||
join(paths.absSrcPath, api.config?.singular ? 'layout' : 'layouts'),
|
||||
join(paths.absSrcPath, 'app.js')
|
||||
]
|
||||
});
|
||||
lodash
|
||||
.uniq(watcherPaths.map(p => winPath(p)))
|
||||
.forEach((p) => {
|
||||
createWatcher(p);
|
||||
});
|
||||
}
|
||||
|
||||
return unwatch;
|
||||
};
|
60
packages/fes-plugin-built-in/src/plugins/features/alias.js
Normal file
60
packages/fes-plugin-built-in/src/plugins/features/alias.js
Normal file
@ -0,0 +1,60 @@
|
||||
import { dirname } from 'path';
|
||||
import { winPath, resolve } from '@umijs/utils';
|
||||
|
||||
export default (api) => {
|
||||
const { paths, pkg, cwd } = api;
|
||||
|
||||
api.describe({
|
||||
key: 'alias',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.object();
|
||||
},
|
||||
default: {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function getUserLibDir({ library }) {
|
||||
if (
|
||||
(pkg.dependencies && pkg.dependencies[library])
|
||||
|| (pkg.devDependencies && pkg.devDependencies[library])
|
||||
// egg project using `clientDependencies` in ali tnpm
|
||||
|| (pkg.clientDependencies && pkg.clientDependencies[library])
|
||||
) {
|
||||
return winPath(
|
||||
dirname(
|
||||
// 通过 resolve 往上找,可支持 lerna 仓库
|
||||
// lerna 仓库如果用 yarn workspace 的依赖不一定在 node_modules,可能被提到根目录,并且没有 link
|
||||
resolve.sync(`${library}/package.json`, {
|
||||
basedir: cwd
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 另一种实现方式:
|
||||
// 提供 projectFirstLibraries 的配置方式,但是不通用,先放插件层实现
|
||||
api.chainWebpack(async (memo) => {
|
||||
const libraries = await api.applyPlugins({
|
||||
key: 'addProjectFirstLibraries',
|
||||
type: api.ApplyPluginsType.add,
|
||||
initialValue: [
|
||||
]
|
||||
});
|
||||
libraries.forEach((library) => {
|
||||
memo.resolve.alias.set(
|
||||
library.name,
|
||||
getUserLibDir({ library: library.name }) || library.path,
|
||||
);
|
||||
});
|
||||
|
||||
// 选择在 chainWebpack 中进行以上 alias 的初始化,是为了支持用户使用 modifyPaths API 对 paths 进行改写
|
||||
memo.resolve.alias.set('@', paths.absSrcPath);
|
||||
memo.resolve.alias.set('@@', paths.absTmpPath);
|
||||
|
||||
return memo;
|
||||
});
|
||||
};
|
44
packages/fes-plugin-built-in/src/plugins/features/analyze.js
Normal file
44
packages/fes-plugin-built-in/src/plugins/features/analyze.js
Normal file
@ -0,0 +1,44 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'analyze',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi
|
||||
.object({
|
||||
analyzerMode: joi.string().valid('server', 'static', 'disabled'),
|
||||
analyzerHost: joi.string(),
|
||||
analyzerPort: joi.alternatives(joi.number(), 'auto'),
|
||||
openAnalyzer: joi.boolean(),
|
||||
generateStatsFile: joi.boolean(),
|
||||
statsFilename: joi.string(),
|
||||
logLevel: joi.string().valid('info', 'warn', 'error', 'silent'),
|
||||
defaultSizes: joi.string().valid('stat', 'parsed', 'gzip')
|
||||
})
|
||||
.unknown(true);
|
||||
},
|
||||
default: {
|
||||
analyzerMode: process.env.ANALYZE_MODE || 'server',
|
||||
analyzerPort: process.env.ANALYZE_PORT || 8888,
|
||||
openAnalyzer: process.env.ANALYZE_OPEN !== 'none',
|
||||
// generate stats file while ANALYZE_DUMP exist
|
||||
generateStatsFile: !!process.env.ANALYZE_DUMP,
|
||||
statsFilename: process.env.ANALYZE_DUMP || 'stats.json',
|
||||
logLevel: process.env.ANALYZE_LOG_LEVEL || 'info',
|
||||
defaultSizes: 'parsed' // stat // gzip
|
||||
}
|
||||
},
|
||||
enableBy: () => !!(process.env.ANALYZE || process.env.ANALYZE_SSR)
|
||||
});
|
||||
api.chainWebpack((webpackConfig, opts) => {
|
||||
const { type } = opts;
|
||||
if (type === 'csr' && !process.env.ANALYZE_SSR) {
|
||||
webpackConfig
|
||||
.plugin('bundle-analyzer')
|
||||
.use(require('umi-webpack-bundle-analyzer').BundleAnalyzerPlugin, [
|
||||
api.config?.analyze || {}
|
||||
]);
|
||||
}
|
||||
return webpackConfig;
|
||||
});
|
||||
};
|
@ -0,0 +1,16 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'autoprefixer',
|
||||
config: {
|
||||
default: {
|
||||
flexbox: 'no-2009'
|
||||
},
|
||||
schema(joi) {
|
||||
return joi
|
||||
.object()
|
||||
.description('postcss autoprefixer, default flexbox: no-2009');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
12
packages/fes-plugin-built-in/src/plugins/features/base.js
Normal file
12
packages/fes-plugin-built-in/src/plugins/features/base.js
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'base',
|
||||
config: {
|
||||
default: '/',
|
||||
schema(joi) {
|
||||
return joi.string();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,31 @@
|
||||
import { join } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
import { winPath } from '@umijs/utils';
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'chainWebpack',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.function();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
api.chainWebpack((webpackConfig) => {
|
||||
const cwd = api.cwd;
|
||||
const prefix = existsSync(join(cwd, 'src')) ? join(cwd, 'src') : cwd;
|
||||
// 添加 .vue 后缀
|
||||
webpackConfig.resolve.extensions.merge([
|
||||
'.vue'
|
||||
]);
|
||||
webpackConfig.module
|
||||
.rule('js-in-node_modules').use('babel-loader').tap((options) => {
|
||||
console.log(options);
|
||||
options.cacheDirectory = winPath(`${prefix}/.fes/.cache/babel-loader`);
|
||||
return options;
|
||||
});
|
||||
|
||||
return webpackConfig;
|
||||
});
|
||||
};
|
11
packages/fes-plugin-built-in/src/plugins/features/chunks.js
Normal file
11
packages/fes-plugin-built-in/src/plugins/features/chunks.js
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'chunks',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.array().items(joi.string());
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
19
packages/fes-plugin-built-in/src/plugins/features/copy.js
Normal file
19
packages/fes-plugin-built-in/src/plugins/features/copy.js
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'copy',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.array().items(
|
||||
joi.alternatives(
|
||||
joi.object({
|
||||
from: joi.string(),
|
||||
to: joi.string()
|
||||
}),
|
||||
joi.string(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'cssLoader',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi
|
||||
.object({
|
||||
url: joi.alternatives(joi.boolean(), joi.function()),
|
||||
import: joi.alternatives(joi.boolean(), joi.function()),
|
||||
modules: joi.alternatives(
|
||||
joi.boolean(),
|
||||
joi.string(),
|
||||
joi.object(),
|
||||
),
|
||||
sourceMap: joi.boolean(),
|
||||
importLoaders: joi.number(),
|
||||
onlyLocals: joi.boolean(),
|
||||
esModule: joi.boolean(),
|
||||
localsConvention: joi
|
||||
.string()
|
||||
.valid(
|
||||
'asIs',
|
||||
'camelCase',
|
||||
'camelCaseOnly',
|
||||
'dashes',
|
||||
'dashesOnly',
|
||||
)
|
||||
})
|
||||
.description(
|
||||
'more css-loader options see https://webpack.js.org/loaders/css-loader/#options',
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
16
packages/fes-plugin-built-in/src/plugins/features/cssnano.js
Normal file
16
packages/fes-plugin-built-in/src/plugins/features/cssnano.js
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
// https://cssnano.co/optimisations/
|
||||
key: 'cssnano',
|
||||
config: {
|
||||
default: {
|
||||
mergeRules: false,
|
||||
minifyFontValues: { removeQuotes: false }
|
||||
},
|
||||
schema(joi) {
|
||||
return joi.object();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
11
packages/fes-plugin-built-in/src/plugins/features/define.js
Normal file
11
packages/fes-plugin-built-in/src/plugins/features/define.js
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'define',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.object();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'devServer',
|
||||
config: {
|
||||
default: {},
|
||||
schema(joi) {
|
||||
return joi
|
||||
.object({
|
||||
port: joi.number().description('devServer port, default 8000'),
|
||||
host: joi.string(),
|
||||
https: joi.alternatives(
|
||||
joi
|
||||
.object({
|
||||
key: joi.string(),
|
||||
cert: joi.string()
|
||||
})
|
||||
.unknown(),
|
||||
joi.boolean(),
|
||||
),
|
||||
headers: joi.object(),
|
||||
writeToDisk: joi.alternatives(joi.boolean(), joi.function())
|
||||
})
|
||||
.description('devServer configs')
|
||||
.unknown(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
11
packages/fes-plugin-built-in/src/plugins/features/devtool.js
Normal file
11
packages/fes-plugin-built-in/src/plugins/features/devtool.js
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'devtool',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.string();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'externals',
|
||||
config: {
|
||||
schema(joi) {
|
||||
// https://webpack.js.org/configuration/externals/#externals
|
||||
return joi.alternatives(joi.object(), joi.string(), joi.function());
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
import { join } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
import { winPath } from '@umijs/utils';
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'extraBabelPlugins',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.array();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
api.modifyBabelOpts((babelOpts) => {
|
||||
const cwd = api.cwd;
|
||||
const prefix = existsSync(join(cwd, 'src')) ? join(cwd, 'src') : cwd;
|
||||
babelOpts.cacheDirectory = process.env.BABEL_CACHE !== 'none'
|
||||
? winPath(`${prefix}/.fes/.cache/babel-loader`)
|
||||
: false;
|
||||
babelOpts.plugins.push(require.resolve('@vue/babel-plugin-jsx'));
|
||||
|
||||
return babelOpts;
|
||||
});
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'extraBabelPresets',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.array();
|
||||
}
|
||||
}
|
||||
});
|
||||
api.modifyBabelPresetOpts(opts => Object.assign({}, opts, {
|
||||
typescript: false,
|
||||
env: {
|
||||
useBuiltIns: 'entry',
|
||||
corejs: 3,
|
||||
modules: false
|
||||
},
|
||||
react: false,
|
||||
reactRemovePropTypes: false,
|
||||
reactRequire: false,
|
||||
svgr: false
|
||||
}));
|
||||
};
|
@ -0,0 +1,11 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'extraPostCSSPlugins',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.array();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
11
packages/fes-plugin-built-in/src/plugins/features/hash.js
Normal file
11
packages/fes-plugin-built-in/src/plugins/features/hash.js
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'hash',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.boolean();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
66
packages/fes-plugin-built-in/src/plugins/features/html.js
Normal file
66
packages/fes-plugin-built-in/src/plugins/features/html.js
Normal file
@ -0,0 +1,66 @@
|
||||
import { resolve, join } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'vueLoader',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi
|
||||
.object({})
|
||||
.description(
|
||||
'more vue-loader options see https://vue-loader.vuejs.org/',
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
api.chainWebpack((webpackConfig) => {
|
||||
const isProd = api.env === 'production';
|
||||
const htmlOptions = {
|
||||
title: api.service.pkg.name,
|
||||
templateParameters: (compilation, assets, pluginOptions) => {
|
||||
// enhance html-webpack-plugin's built in template params
|
||||
let stats;
|
||||
return Object.assign({
|
||||
// make stats lazy as it is expensive
|
||||
get webpack() {
|
||||
// eslint-disable-next-line
|
||||
return stats || (stats = compilation.getStats().toJson());
|
||||
},
|
||||
compilation,
|
||||
webpackConfig: compilation.options,
|
||||
htmlWebpackPlugin: {
|
||||
files: assets,
|
||||
options: pluginOptions
|
||||
}
|
||||
}, api.config.html);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (isProd) {
|
||||
Object.assign(htmlOptions, {
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
collapseBooleanAttributes: true,
|
||||
removeScriptTypeAttributes: true
|
||||
// more options:
|
||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// resolve HTML file(s)
|
||||
const htmlPath = join(api.paths.cwd, 'public/index.html');
|
||||
const defaultHtmlPath = resolve(__dirname, 'index-default.html');
|
||||
htmlOptions.template = existsSync(htmlPath)
|
||||
? htmlPath
|
||||
: defaultHtmlPath;
|
||||
|
||||
webpackConfig
|
||||
.plugin('html')
|
||||
.use(require('html-webpack-plugin'), [htmlOptions]);
|
||||
});
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Fes App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,11 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'inlineLimit',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.number();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,11 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'lessLoader',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.object();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'mountElementId',
|
||||
config: {
|
||||
default: '#app',
|
||||
schema(joi) {
|
||||
return joi.string().allow('');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'nodeModulesTransform',
|
||||
config: {
|
||||
default: {
|
||||
type: 'all',
|
||||
exclude: []
|
||||
},
|
||||
schema(joi) {
|
||||
return joi.object({
|
||||
type: joi.string().valid('all', 'none'),
|
||||
exclude: joi.array().items(joi.string())
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'outputPath',
|
||||
config: {
|
||||
default: 'dist',
|
||||
schema(joi) {
|
||||
return joi
|
||||
.string()
|
||||
.not('src', 'public', 'pages', 'mock', 'config')
|
||||
.allow('');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
11
packages/fes-plugin-built-in/src/plugins/features/plugins.js
Normal file
11
packages/fes-plugin-built-in/src/plugins/features/plugins.js
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'plugins',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.array().items(joi.string());
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,11 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'postcssLoader',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.object();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
18
packages/fes-plugin-built-in/src/plugins/features/proxy.js
Normal file
18
packages/fes-plugin-built-in/src/plugins/features/proxy.js
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'proxy',
|
||||
config: {
|
||||
onChange: () => {
|
||||
const server = api.getServer();
|
||||
if (server) {
|
||||
// refrest proxy service
|
||||
server.setupProxy(api.config.proxy, true);
|
||||
}
|
||||
},
|
||||
schema(joi) {
|
||||
return joi.object();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'publicPath',
|
||||
config: {
|
||||
default: '/',
|
||||
schema(joi) {
|
||||
return joi
|
||||
.string()
|
||||
.regex(/\/$/)
|
||||
.error(new Error('config.publicPath must end with /.'));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,10 @@
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'styleLoader',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.object();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
19
packages/fes-plugin-built-in/src/plugins/features/targets.js
Normal file
19
packages/fes-plugin-built-in/src/plugins/features/targets.js
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'targets',
|
||||
config: {
|
||||
default: {
|
||||
node: true,
|
||||
chrome: 49,
|
||||
firefox: 64,
|
||||
safari: 10,
|
||||
edge: 13,
|
||||
ios: 10
|
||||
},
|
||||
schema(joi) {
|
||||
return joi.object();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,11 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'terserOptions',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.object();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
11
packages/fes-plugin-built-in/src/plugins/features/theme.js
Normal file
11
packages/fes-plugin-built-in/src/plugins/features/theme.js
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'theme',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.object().pattern(joi.string(), joi.string());
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,35 @@
|
||||
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: 'vueLoader',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi
|
||||
.object({})
|
||||
.description(
|
||||
'more vue-loader options see https://vue-loader.vuejs.org/',
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
api.chainWebpack((webpackConfig) => {
|
||||
// 添加 .vue 后缀
|
||||
webpackConfig.module
|
||||
.rule('vue')
|
||||
.test(/\.vue$/)
|
||||
.use('vue-loader')
|
||||
.loader(require.resolve('vue-loader'))
|
||||
.options({
|
||||
babelParserPlugins: ['jsx', 'classProperties', 'decorators-legacy']
|
||||
})
|
||||
.end()
|
||||
.end();
|
||||
|
||||
webpackConfig
|
||||
.plugin('vue-loader')
|
||||
.use(require('vue-loader').VueLoaderPlugin);
|
||||
|
||||
return webpackConfig;
|
||||
});
|
||||
};
|
@ -3,7 +3,7 @@ import { readFileSync } from 'fs';
|
||||
import {
|
||||
join
|
||||
} from 'path';
|
||||
import { routesToJSON } from '../../../route';
|
||||
import { routesToJSON } from '@webank/fes-core';
|
||||
|
||||
export default function (api) {
|
||||
const {
|
@ -4,6 +4,7 @@ import { existsSync, readFileSync, writeFileSync } from 'fs';
|
||||
|
||||
export default function (api) {
|
||||
[
|
||||
'onExit',
|
||||
'onGenerateFiles',
|
||||
'addFesExports',
|
||||
'addRuntimePluginKey',
|
||||
@ -12,7 +13,18 @@ export default function (api) {
|
||||
'addEntryImports',
|
||||
'addEntryCodeAhead',
|
||||
'addEntryCode',
|
||||
'modifyRoutes'
|
||||
'addBeforeMiddewares',
|
||||
'addMiddewares',
|
||||
'modifyRoutes',
|
||||
'modifyBundler',
|
||||
'modifyBundleImplementor',
|
||||
'modifyBundleConfigOpts',
|
||||
'modifyBundleConfig',
|
||||
'modifyBundleConfigs',
|
||||
'modifyBabelOpts',
|
||||
'modifyBabelPresetOpts',
|
||||
'chainWebpack',
|
||||
'addTmpGenerateWatcherPaths'
|
||||
].forEach((name) => {
|
||||
api.registerMethod({ name });
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
import { getRoutes } from '../route';
|
||||
import { getRoutes } from '@webank/fes-core';
|
||||
|
||||
export default function (api) {
|
||||
api.describe({
|
@ -30,11 +30,9 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"@webank/eslint-config-webank": "^0.1.4",
|
||||
"csp-html-webpack-plugin": "^4.0.0"
|
||||
"@webank/eslint-config-webank": "^0.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime-corejs3": "^7.11.2",
|
||||
"vue": "^3.0.2",
|
||||
"@webank/fes": "^2.0.0"
|
||||
}
|
||||
|
6
packages/fes/.fatherrc.js
Normal file
6
packages/fes/.fatherrc.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
cjs: { type: 'babel', lazy: true },
|
||||
esm: { type: 'rollup' },
|
||||
disableTypeCheck: false,
|
||||
extraExternals: ['@@/core/fesExports'],
|
||||
};
|
13
packages/fes/bin/fes.js
Executable file
13
packages/fes/bin/fes.js
Executable file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const resolveCwd = require('resolve-cwd');
|
||||
|
||||
const { name, bin } = require('../package.json');
|
||||
|
||||
const localCLI = resolveCwd.silent(`${name}/${bin.fes}`);
|
||||
if (!process.env.USE_GLOBAL_UMI && localCLI && localCLI !== __filename) {
|
||||
// eslint-disable-next-line
|
||||
require(localCLI);
|
||||
} else {
|
||||
require('../lib/cli');
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const commander = require('commander');
|
||||
|
||||
const pkg = require('../package.json');
|
||||
const generateConfig = require('../build/helpers/config');
|
||||
const log = require('../build/helpers/log');
|
||||
|
||||
commander.usage('<command> [options]')
|
||||
.version(pkg.version, '-v, --vers')
|
||||
.option('-e, --env <env>', '配置环境 local(本地) | sit(测试) | prod(生产)')
|
||||
.description(pkg.description);
|
||||
|
||||
commander.command('init [name]')
|
||||
.description('创建项目')
|
||||
.action(async (name) => {
|
||||
const projectInit = require('../build/tasks/init');
|
||||
const config = generateConfig('init');
|
||||
await projectInit(config, name);
|
||||
});
|
||||
|
||||
commander.command('update')
|
||||
.description('将 fes2 项目升级到 fes3')
|
||||
.action(() => {
|
||||
const update = require('../build/tasks/update');
|
||||
const config = generateConfig('update');
|
||||
update(config);
|
||||
});
|
||||
|
||||
commander.command('dev')
|
||||
.description('开发调试, 默认 local')
|
||||
.action(() => {
|
||||
const dev = require('../build/tasks/dev');
|
||||
const config = generateConfig('dev', commander.env || 'local');
|
||||
dev(config);
|
||||
});
|
||||
|
||||
commander.command('build')
|
||||
.description('打包压缩,默认 prod')
|
||||
.action(() => {
|
||||
const build = require('../build/tasks/build');
|
||||
const config = generateConfig('build', commander.env || 'prod');
|
||||
build(config);
|
||||
});
|
||||
|
||||
commander.parse(process.argv);
|
||||
|
||||
|
||||
if (!process.argv.slice(2).length) {
|
||||
commander.outputHelp((text) => {
|
||||
log.message(text);
|
||||
return '';
|
||||
});
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const browsers = require('../helpers/browser');
|
||||
|
||||
module.ex = {
|
||||
plugins: [
|
||||
autoprefixer({ browsers })
|
||||
]
|
||||
};
|
@ -1,486 +0,0 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const merge = require('webpack-merge');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const { VueLoaderPlugin } = require('vue-loader');
|
||||
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
|
||||
const FriendlyErrorsPlugin = require('@soda/friendly-errors-webpack-plugin');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const OptimizeCssnanoPlugin = require('@intervolga/optimize-cssnano-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const HtmlPlugin = require('html-webpack-plugin');
|
||||
const CompressionWebpackPlugin = require('compression-webpack-plugin');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const browsers = require('../helpers/browser');
|
||||
|
||||
|
||||
function handleGzipCompress(compress) {
|
||||
if (!compress) return false;
|
||||
if (typeof compress === 'boolean') {
|
||||
return {};
|
||||
}
|
||||
return compress;
|
||||
}
|
||||
|
||||
|
||||
module.exports = function webpackConfig(configs, webpack, mode) {
|
||||
let template = path.resolve(
|
||||
configs.folders.PROJECT_DIR,
|
||||
'./publish/index.html'
|
||||
);
|
||||
if (!fs.existsSync(template)) {
|
||||
template = path.resolve(configs.folders.FES_DIR, './src/index.html');
|
||||
}
|
||||
|
||||
const isDev = mode === 'dev';
|
||||
const isBuild = mode === 'build';
|
||||
|
||||
const gzipCompress = handleGzipCompress(configs.compress);
|
||||
|
||||
const presets = [
|
||||
[
|
||||
require.resolve('@babel/preset-env')
|
||||
]
|
||||
];
|
||||
const plugins = [
|
||||
[require.resolve('@vue/babel-plugin-jsx')],
|
||||
[
|
||||
require.resolve('@babel/plugin-transform-runtime'), {
|
||||
corejs: 3
|
||||
}
|
||||
],
|
||||
require.resolve('@babel/plugin-proposal-object-rest-spread'),
|
||||
require.resolve('@babel/plugin-syntax-dynamic-import')
|
||||
];
|
||||
const cssloaders = [
|
||||
isDev
|
||||
? {
|
||||
loader: require.resolve('vue-style-loader'),
|
||||
options: {
|
||||
sourceMap: false,
|
||||
shadowMode: false
|
||||
}
|
||||
}
|
||||
: {
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
publicPath: '../'
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
options: {
|
||||
sourceMap: false,
|
||||
importLoaders: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: require.resolve('postcss-loader'),
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: [
|
||||
autoprefixer({ browsers })
|
||||
]
|
||||
},
|
||||
sourceMap: false
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
const baseConfig = {
|
||||
mode: isDev ? 'development' : 'production',
|
||||
|
||||
context: path.resolve(configs.folders.PROJECT_DIR),
|
||||
|
||||
entry: {
|
||||
app: [
|
||||
path.resolve(configs.folders.PROJECT_DIR, './src/.fes/fes.js')
|
||||
]
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx', '.vue', '.json'],
|
||||
alias: {
|
||||
projectRoot: configs.folders.PROJECT_DIR,
|
||||
'@': path.resolve(configs.folders.PROJECT_DIR, 'src'),
|
||||
assets: path.resolve(configs.folders.PROJECT_DIR, './src/assets/')
|
||||
}
|
||||
},
|
||||
|
||||
output: {
|
||||
globalObject: 'this',
|
||||
filename: isDev ? 'js/[name].js' : 'js/[name].[contenthash:8].js',
|
||||
chunkFilename: isDev ? 'js/[name].chunk.js' : 'js/[name].[contenthash:8].js',
|
||||
path: configs.folders.PROJECT_DIST_DIR,
|
||||
publicPath: isDev ? '/' : './'
|
||||
},
|
||||
|
||||
module: {
|
||||
// noParse: /^(vue|vue-router|vuex|vuex-router-sync|axios)$/,
|
||||
// noParse: /fes-runtime/,
|
||||
rules: [
|
||||
/* config.module.rule('vue') */
|
||||
{
|
||||
test: /\.vue$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('cache-loader'),
|
||||
options: {
|
||||
cacheDirectory: path.resolve(configs.folders.PROJECT_DIR, 'node_modules/.cache/vue-loader')
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: require.resolve('vue-loader'),
|
||||
options: {
|
||||
shadowMode: true,
|
||||
cacheDirectory: path.resolve(configs.folders.PROJECT_DIR, 'node_modules/.cache/vue-loader'),
|
||||
babelParserPlugins: ['jsx', 'classProperties', 'decorators-legacy']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
/* config.module.rule('images') */
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('url-loader'),
|
||||
options: {
|
||||
limit: 4096,
|
||||
fallback: {
|
||||
loader: require.resolve('file-loader'),
|
||||
options: {
|
||||
name: isDev ? 'img/[name].[ext]' : 'img/[name].[hash:8].[ext]'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
/* config.module.rule('svg') */
|
||||
{
|
||||
test: /\.(svg)(\?.*)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('file-loader'),
|
||||
options: {
|
||||
name: isDev ? 'img/[name].[ext]' : 'img/[name].[hash:8].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
/* config.module.rule('media') */
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('url-loader'),
|
||||
options: {
|
||||
limit: 4096,
|
||||
fallback: {
|
||||
loader: require.resolve('file-loader'),
|
||||
options: {
|
||||
name: isDev ? 'media/[name].[ext]' : 'media/[name].[hash:8].[ext]'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
/* config.module.rule('fonts') */
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('url-loader'),
|
||||
options: {
|
||||
limit: 4096,
|
||||
fallback: {
|
||||
loader: require.resolve('file-loader'),
|
||||
options: {
|
||||
name: isDev ? 'fonts/[name].[ext]' : 'fonts/[name].[hash:8].[ext]'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
/* config.module.rule('css') */
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: cssloaders
|
||||
},
|
||||
|
||||
/* config.module.rule('postcss') */
|
||||
{
|
||||
test: /\.p(ost)?css$/,
|
||||
use: cssloaders
|
||||
},
|
||||
|
||||
/* config.module.rule('less') */
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: cssloaders.concat([
|
||||
{
|
||||
loader: require.resolve('less-loader'),
|
||||
options: {
|
||||
sourceMap: false,
|
||||
javascriptEnabled: true
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
/* config.module.rule('stylus') */
|
||||
{
|
||||
test: /\.styl(us)?$/,
|
||||
use: cssloaders.concat([
|
||||
{
|
||||
loader: require.resolve('stylus-loader'),
|
||||
options: {
|
||||
sourceMap: false,
|
||||
preferPathResolver: 'webpack'
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
/* config.module.rule('js') */
|
||||
{
|
||||
test: /\.m?jsx?$/,
|
||||
include(filePath) {
|
||||
if (filePath.startsWith(path.resolve(process.cwd(), 'src'))) {
|
||||
return true;
|
||||
}
|
||||
if (/fes-core.?src/.test(filePath)) {
|
||||
return true;
|
||||
}
|
||||
if (/fes-plugin-[a-z-]+.?(src|index)/.test(filePath)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('cache-loader'),
|
||||
options: {
|
||||
cacheDirectory: path.resolve(configs.folders.PROJECT_DIR, 'node_modules/.cache/babel-loader')
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: require.resolve('thread-loader')
|
||||
},
|
||||
{
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: {
|
||||
presets,
|
||||
plugins
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
|
||||
devtool: isDev && 'cheap-module-eval-source-map',
|
||||
|
||||
plugins: [
|
||||
|
||||
/* config.plugin('progress') */
|
||||
new webpack.ProgressPlugin(),
|
||||
|
||||
/* config.plugin('vue-loader') */
|
||||
new VueLoaderPlugin(),
|
||||
|
||||
/* config.plugin('define') */
|
||||
new webpack.DefinePlugin({
|
||||
__VUE_OPTIONS_API__: true,
|
||||
__VUE_PROD_DEVTOOLS__: false,
|
||||
'process.env': {
|
||||
// NODE_ENV: isDev ? 'development' : 'production',
|
||||
env: JSON.stringify(configs.env),
|
||||
command: JSON.stringify(configs.command)
|
||||
}
|
||||
}),
|
||||
|
||||
/* config.plugin('clean dist') */
|
||||
isBuild && new CleanWebpackPlugin(),
|
||||
|
||||
/* config.plugin('extract-css') */
|
||||
isBuild
|
||||
&& new MiniCssExtractPlugin({
|
||||
filename: 'css/[name].[contenthash:8].css',
|
||||
chunkFilename: 'css/[name].[contenthash:8].css'
|
||||
}),
|
||||
|
||||
/* config.plugin('Copy static') */
|
||||
isBuild
|
||||
&& new CopyPlugin([
|
||||
{
|
||||
from: configs.folders.PROJECT_STATIC_DIR,
|
||||
to: path.resolve(
|
||||
configs.folders.PROJECT_DIST_DIR,
|
||||
'static'
|
||||
)
|
||||
}
|
||||
]),
|
||||
|
||||
/* config.plugin('optimize-css') */
|
||||
isBuild
|
||||
&& new OptimizeCssnanoPlugin({
|
||||
sourceMap: false,
|
||||
cssnanoOptions: {
|
||||
preset: [
|
||||
'default',
|
||||
{
|
||||
mergeLonghand: false,
|
||||
cssDeclarationSorter: false
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
|
||||
/* config.plugin('hash-module-ids') */
|
||||
isBuild
|
||||
&& new webpack.HashedModuleIdsPlugin({
|
||||
hashDigest: 'hex'
|
||||
}),
|
||||
|
||||
/* config.plugin('固定一下 chunk id') */
|
||||
isBuild
|
||||
&& new webpack.NamedChunksPlugin((chunk) => {
|
||||
if (chunk.name) {
|
||||
return chunk.name;
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
const hash = require('hash-sum');
|
||||
const joinedHash = hash(
|
||||
Array.from(chunk.modulesIterable, m => m.id).join('_')
|
||||
);
|
||||
return `chunk-${joinedHash}`;
|
||||
}),
|
||||
|
||||
// /* config.plugin('Copyright') */
|
||||
// isBuild
|
||||
// && new webpack.BannerPlugin(''),
|
||||
|
||||
/* config.plugin('case-sensitive-paths') */
|
||||
new CaseSensitivePathsPlugin(),
|
||||
|
||||
/* config.plugin('friendly-errors') */
|
||||
new FriendlyErrorsPlugin(),
|
||||
|
||||
isBuild && gzipCompress && new CompressionWebpackPlugin({ // gzip 压缩
|
||||
filename: '[path][base].gz',
|
||||
test: /\.js$|\.html$|\.css/,
|
||||
threshold: 10240,
|
||||
minRatio: 0.8,
|
||||
...gzipCompress
|
||||
}),
|
||||
|
||||
/* config.plugin('index.html') */
|
||||
new HtmlPlugin({
|
||||
template,
|
||||
minify: isBuild && {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
collapseBooleanAttributes: true,
|
||||
removeScriptTypeAttributes: true
|
||||
}
|
||||
})
|
||||
|
||||
]
|
||||
};
|
||||
|
||||
if (isBuild) {
|
||||
baseConfig.optimization = {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
test: /\.m?js(\?.*)?$/i,
|
||||
chunkFilter: () => true,
|
||||
warningsFilter: () => true,
|
||||
extractComments: false,
|
||||
sourceMap: true,
|
||||
cache: true,
|
||||
cacheKeys: defaultCacheKeys => defaultCacheKeys,
|
||||
parallel: true,
|
||||
include: undefined,
|
||||
exclude: undefined,
|
||||
minify: undefined,
|
||||
terserOptions: {
|
||||
output: {
|
||||
comments: /^\**!|@preserve|@license|@cc_on/i
|
||||
},
|
||||
compress: {
|
||||
arrows: false,
|
||||
collapse_vars: false,
|
||||
comparisons: false,
|
||||
computed_props: false,
|
||||
hoist_funs: false,
|
||||
hoist_props: false,
|
||||
hoist_vars: false,
|
||||
inline: false,
|
||||
loops: false,
|
||||
negate_iife: false,
|
||||
properties: false,
|
||||
reduce_funcs: false,
|
||||
reduce_vars: false,
|
||||
switches: false,
|
||||
toplevel: false,
|
||||
typeofs: false,
|
||||
booleans: true,
|
||||
if_return: true,
|
||||
sequences: true,
|
||||
unused: true,
|
||||
conditionals: true,
|
||||
dead_code: true,
|
||||
evaluate: true
|
||||
},
|
||||
mangle: {
|
||||
safari10: true
|
||||
}
|
||||
}
|
||||
})
|
||||
],
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
vendors: {
|
||||
name: 'chunk-vendors',
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
priority: -10,
|
||||
chunks: 'initial'
|
||||
},
|
||||
common: {
|
||||
name: 'chunk-common',
|
||||
minChunks: 2,
|
||||
priority: -20,
|
||||
chunks: 'initial',
|
||||
reuseExistingChunk: true
|
||||
}
|
||||
}
|
||||
},
|
||||
runtimeChunk: true
|
||||
};
|
||||
}
|
||||
|
||||
baseConfig.plugins = baseConfig.plugins.filter(plu => plu !== false);
|
||||
|
||||
let advancedConfig = {};
|
||||
const projectWebpackConfigFile = path.resolve(configs.folders.PROJECT_DIR, 'webpack.config.js');
|
||||
if (fs.existsSync(projectWebpackConfigFile)) {
|
||||
console.log('[init] 加载项目个性webpack配置文件');
|
||||
// eslint-disable-next-line
|
||||
advancedConfig = require(projectWebpackConfigFile)(mode, configs, webpack);
|
||||
}
|
||||
|
||||
return merge(baseConfig, advancedConfig);
|
||||
};
|
@ -1,8 +0,0 @@
|
||||
module.exports = [
|
||||
'Chrome >= 46',
|
||||
'Firefox >= 45',
|
||||
'Safari >= 10',
|
||||
'Edge >= 13',
|
||||
'iOS >= 10',
|
||||
'Electron >= 0.36'
|
||||
];
|
@ -1,41 +0,0 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
function generateConfig(command, env) {
|
||||
// cli目录
|
||||
const CLI_DIR = path.dirname(path.dirname(fs.realpathSync(process.argv[1])));
|
||||
// 解决git-bash目录问题
|
||||
const PROJECT_DIR = process.env.PWD || process.cwd();
|
||||
const FES_DIR = path.resolve(PROJECT_DIR, './node_modules/@webank/fes-core');
|
||||
|
||||
const PROJECT_DIST_DIR = path.resolve(PROJECT_DIR, 'dist');
|
||||
const PROJECT_TMP_DIR = path.resolve(PROJECT_DIR, './.fes');
|
||||
const PROJECT_PAGE_DIR = path.resolve(PROJECT_DIR, './src/pages');
|
||||
const PROJECT_CPN_DIR = path.resolve(PROJECT_DIR, './src/components');
|
||||
const PROJECT_STATIC_DIR = path.join(PROJECT_DIR, './src/static');
|
||||
const projectName = path.basename(PROJECT_DIR);
|
||||
|
||||
const fesConfigFile = path.join(PROJECT_DIR, 'fes.config.js');
|
||||
|
||||
const config = {
|
||||
command,
|
||||
env,
|
||||
port: 5000,
|
||||
projectName,
|
||||
folders: {
|
||||
CLI_DIR,
|
||||
FES_DIR,
|
||||
PROJECT_DIR,
|
||||
PROJECT_STATIC_DIR,
|
||||
PROJECT_DIST_DIR,
|
||||
PROJECT_TMP_DIR,
|
||||
PROJECT_PAGE_DIR,
|
||||
PROJECT_CPN_DIR
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line
|
||||
const fesCofig = require(fesConfigFile);
|
||||
return Object.assign({}, config, fesCofig);
|
||||
}
|
||||
|
||||
module.exports = generateConfig;
|
@ -1,49 +0,0 @@
|
||||
const
|
||||
http = require('http');
|
||||
const webpack = require('webpack');
|
||||
const express = require('express');
|
||||
const open = require('open');
|
||||
const path = require('path');
|
||||
const webpackHotMiddleware = require('webpack-hot-middleware');
|
||||
const webpackDevMiddleware = require('webpack-dev-middleware');
|
||||
const initMock = require('../mock/init.js');
|
||||
|
||||
|
||||
module.exports = function createDevServer(port, defaultConfig) {
|
||||
const hotMiddlewarePath = require.resolve('webpack-hot-middleware');
|
||||
defaultConfig.entry.app.unshift(`${hotMiddlewarePath.replace(path.basename(hotMiddlewarePath), '')}client?reload=true`);
|
||||
defaultConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||
defaultConfig.plugins.push(new webpack.NamedModulesPlugin());
|
||||
|
||||
const app = express();
|
||||
const compiler = webpack(defaultConfig);
|
||||
|
||||
// devServer 自带支持,添加自定义插件。
|
||||
app.use(webpackDevMiddleware(compiler, {
|
||||
lazy: false,
|
||||
logLevel: 'silent',
|
||||
watchOptions: {
|
||||
aggregateTimeout: 300,
|
||||
poll: 1000
|
||||
},
|
||||
stats: {
|
||||
colors: true,
|
||||
chunks: false,
|
||||
timings: true
|
||||
},
|
||||
publicPath: defaultConfig.output.publicPath
|
||||
}));
|
||||
|
||||
app.use(webpackHotMiddleware(compiler, {
|
||||
log: false
|
||||
}));
|
||||
app.use('/static', express.static('src/static'));
|
||||
|
||||
|
||||
// 初始化Mock数据
|
||||
initMock(app);
|
||||
|
||||
defaultConfig.open && open(`http://localhost:${port}`);
|
||||
|
||||
http.createServer(app).listen(port);
|
||||
};
|
@ -1,25 +0,0 @@
|
||||
const net = require('net');
|
||||
|
||||
function checkout(port) {
|
||||
return new Promise((resolve) => {
|
||||
const server = net.createServer();
|
||||
server.once('error', (err) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
resolve(checkout(port + 1));
|
||||
}
|
||||
});
|
||||
|
||||
server.once('listening', () => {
|
||||
server.close(() => {
|
||||
resolve(port);
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(port);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function getPort(basePort) {
|
||||
basePort = basePort || 5000;
|
||||
return checkout(basePort);
|
||||
};
|
@ -1,13 +0,0 @@
|
||||
const chalk = require('chalk');
|
||||
|
||||
module.exports = {
|
||||
error(msg) {
|
||||
return console.log(chalk.red(msg));
|
||||
},
|
||||
warn(msg) {
|
||||
return console.log(chalk.yellow(msg));
|
||||
},
|
||||
message(msg) {
|
||||
return console.log(chalk.cyan(msg));
|
||||
}
|
||||
};
|
@ -1,142 +0,0 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const httpProxy = require('http-proxy');
|
||||
const url = require('url');
|
||||
|
||||
const util = require('./util');
|
||||
|
||||
const proxy = httpProxy.createProxyServer();
|
||||
global.router = express.Router();
|
||||
|
||||
/**
|
||||
* 数据模拟函数
|
||||
*/
|
||||
function cgiMock() {
|
||||
// eslint-disable-next-line
|
||||
const option = getOption(arguments);
|
||||
|
||||
if (!option.url || !option.result) {
|
||||
return;
|
||||
}
|
||||
|
||||
// option.method is one of ['get','post','delete','put'...]
|
||||
const method = option.method || 'use';
|
||||
|
||||
global.router[method.toLowerCase()](option.url, (req, res) => {
|
||||
setTimeout(() => {
|
||||
// set header
|
||||
res.set(option.headers);
|
||||
|
||||
// set Content-Type
|
||||
option.type && res.type(option.type);
|
||||
|
||||
// set status code
|
||||
res.status(option.statusCode);
|
||||
|
||||
// set cookie
|
||||
util.each(option.cookies, (item) => {
|
||||
const name = item.name;
|
||||
const value = item.value;
|
||||
delete item.name;
|
||||
delete item.value;
|
||||
res.cookie(name, value, item);
|
||||
});
|
||||
|
||||
// do result
|
||||
if (util.isFunction(option.result)) {
|
||||
option.result(req, res);
|
||||
} else if (util.isArray(option.result) || util.isObject(option.result)) {
|
||||
!option.type && res.type('json');
|
||||
res.json(option.result);
|
||||
} else {
|
||||
!option.type && res.type('text');
|
||||
res.send(option.result.toString());
|
||||
}
|
||||
}, option.timeout);
|
||||
});
|
||||
}
|
||||
|
||||
// 根据参数个数获取配置
|
||||
function getOption(arg) {
|
||||
const len = arg.length;
|
||||
// 默认配置
|
||||
const option = {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache'
|
||||
},
|
||||
statusCode: 200,
|
||||
cookies: [],
|
||||
timeout: 0
|
||||
};
|
||||
if (len === 0) {
|
||||
return cgiMock;
|
||||
} if (len === 1) {
|
||||
const newOption = arg[0];
|
||||
if (util.isObject(newOption)) {
|
||||
util.each(newOption, (value, key) => {
|
||||
if (key === 'headers') {
|
||||
util.each(newOption.headers, (headervalue, headerkey) => {
|
||||
option.headers[headerkey] = newOption.headers[headerkey];
|
||||
});
|
||||
} else {
|
||||
option[key] = newOption[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
option.url = arg[0];
|
||||
option.result = arg[1];
|
||||
}
|
||||
return option;
|
||||
}
|
||||
|
||||
// 把基于 cgiMockfile 的相对绝对转成绝对路径
|
||||
function parsePath(value) {
|
||||
return path.join(global.cgiMockFilePath, value);
|
||||
}
|
||||
|
||||
|
||||
// log proxy data
|
||||
proxy.on('open', (proxySocket) => {
|
||||
proxySocket.on('data', (chunk) => {
|
||||
console.log(chunk.toString());
|
||||
});
|
||||
});
|
||||
proxy.on('proxyRes', (proxyRes) => {
|
||||
console.log('RAW Response from the target', JSON.stringify(proxyRes.headers, true, 2));
|
||||
const cookie = proxyRes.headers['set-cookie'];
|
||||
if (cookie && cookie.length > 0) {
|
||||
for (let i = 0; i < cookie.length; i++) {
|
||||
cookie[i] = cookie[i].replace('Secure', '');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
proxy.on('error', (e) => {
|
||||
console.log(e);
|
||||
});
|
||||
|
||||
// 规则之外的请求转发
|
||||
cgiMock.proxy = function (host) {
|
||||
process.nextTick(() => {
|
||||
global.router.use((req, res) => {
|
||||
proxy.web(req, res, {
|
||||
target: host,
|
||||
secure: false
|
||||
});
|
||||
});
|
||||
});
|
||||
proxy.on('proxyReq', (proxyReq) => {
|
||||
proxyReq.setHeader('Host', url.parse(host).host);
|
||||
});
|
||||
};
|
||||
|
||||
// 读取文件内容
|
||||
cgiMock.file = function (file) {
|
||||
return fs.readFileSync(parsePath(file));
|
||||
};
|
||||
|
||||
cgiMock.prefix = '/';
|
||||
|
||||
module.exports = cgiMock;
|
@ -1,100 +0,0 @@
|
||||
const Mock = require('mockjs');
|
||||
const faker = require('faker');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const logger = require('morgan');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const bodyParser = require('body-parser');
|
||||
const onFinished = require('on-finished');
|
||||
|
||||
const util = require('./util');
|
||||
const cgiMock = require('./cgiMock');
|
||||
const log = require('../helpers/log');
|
||||
|
||||
const main = {
|
||||
init(app, argv, cwd) {
|
||||
const defaultCgiMockFile = path.join(process.cwd(), 'mock.js');
|
||||
if (fs.existsSync(defaultCgiMockFile)) {
|
||||
this.app = app;
|
||||
this.argv = argv;
|
||||
this.cwd = cwd;
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(
|
||||
bodyParser.urlencoded({
|
||||
extended: false
|
||||
})
|
||||
);
|
||||
app.use(cookieParser());
|
||||
|
||||
this.customRoute();
|
||||
}
|
||||
},
|
||||
|
||||
customRoute() {
|
||||
const argv = this.argv;
|
||||
const defaultCgiMockFile = path.join(process.cwd(), 'mock.js');
|
||||
const home = process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'];
|
||||
|
||||
let cgiMockFile;
|
||||
if (argv) {
|
||||
if (argv.f) {
|
||||
if (process.platform === 'win32') {
|
||||
cgiMockFile = path.resolve(this.cwd, this.argv.f);
|
||||
} else if (argv.f[0] === '~') {
|
||||
cgiMockFile = path.resolve(
|
||||
home,
|
||||
argv.f.replace(/^~\//, '')
|
||||
);
|
||||
} else {
|
||||
cgiMockFile = path.resolve(this.cwd, this.argv.f);
|
||||
}
|
||||
} else {
|
||||
cgiMockFile = defaultCgiMockFile;
|
||||
}
|
||||
} else {
|
||||
cgiMockFile = defaultCgiMockFile;
|
||||
}
|
||||
global.cgiMockFilePath = path.resolve(cgiMockFile, '..');
|
||||
|
||||
const loadRouteConfig = function () {
|
||||
util.cleanCache(cgiMockFile);
|
||||
try {
|
||||
if (!fs.existsSync(cgiMockFile)) {
|
||||
log.error('[WARN] 不存在mock.js文件');
|
||||
} else {
|
||||
// eslint-disable-next-line
|
||||
const projectMock = require(cgiMockFile);
|
||||
if (util.isFunction(projectMock)) {
|
||||
global.router.stack = [];
|
||||
projectMock(cgiMock, Mock, faker);
|
||||
log.message('[SUCCESS] mock.js 加载成功');
|
||||
} else {
|
||||
log.error(
|
||||
`[ERROR] mock.js cannot be ${typeof projectMock}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log.error('[ERROR] mock.js 有误,请检查');
|
||||
log.error(JSON.stringify(e));
|
||||
}
|
||||
};
|
||||
|
||||
loadRouteConfig();
|
||||
this.app.use(cgiMock.prefix, (req, res, next) => {
|
||||
onFinished(res, () => {
|
||||
loadRouteConfig();
|
||||
});
|
||||
global.router(req, res, next);
|
||||
});
|
||||
|
||||
util.watchFile(cgiMockFile, () => {
|
||||
log.message('[INFO] mock.js 发生变化');
|
||||
loadRouteConfig();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = main.init.bind(main);
|
@ -1,17 +0,0 @@
|
||||
|
||||
|
||||
const express = require('express');
|
||||
const argv = require('yargs').argv;
|
||||
|
||||
const port = argv.p || 8888;
|
||||
const cwd = process.cwd();
|
||||
|
||||
const app = express();
|
||||
const init = require('../init');
|
||||
|
||||
init(app, argv, cwd);
|
||||
|
||||
app.set('port', port);
|
||||
app.listen(port, () => {
|
||||
console.log(`cgiMock server listening on ${port}`);
|
||||
});
|
@ -1,64 +0,0 @@
|
||||
module.exports = function mock(cgiMock, Mock) {
|
||||
const Random = Mock.Random;
|
||||
|
||||
// 前缀,全局(可选)
|
||||
cgiMock.prefix = '/prefix';
|
||||
|
||||
// 返回一个数字
|
||||
cgiMock('/number', 123);
|
||||
|
||||
// 返回一个json
|
||||
cgiMock({
|
||||
url: '/json',
|
||||
result: {
|
||||
code: '400101', msg: "不合法的请求:Missing cookie 'wb_app_id' for method parameter of type String", transactionTime: '20170309171146', success: false
|
||||
}
|
||||
});
|
||||
|
||||
// 利用mock.js 产生随机文本
|
||||
cgiMock('/text', Random.cparagraph());
|
||||
|
||||
// 返回一个字符串 利用mock.js 产生随机字符
|
||||
cgiMock('/string', Mock.mock({
|
||||
'string|1-10': '★'
|
||||
}));
|
||||
|
||||
|
||||
// 正则匹配url, 返回一个字符串
|
||||
cgiMock(/\/abc|\/xyz/, 'regexp test!');
|
||||
|
||||
// option.result 参数如果是一个函数, 可以实现自定义返回内容, 接收的参数是是经过 express 封装的 req 和 res 对象.
|
||||
cgiMock(/\/function$/, (req, res) => {
|
||||
res.send('function test');
|
||||
});
|
||||
|
||||
// 返回文本 fs.readFileSync
|
||||
cgiMock('/file', cgiMock.file('./test.json'));
|
||||
|
||||
// 更复杂的规则配置
|
||||
cgiMock({
|
||||
url: /\/who/,
|
||||
method: 'GET',
|
||||
result(req, res) {
|
||||
if (req.query.name === 'kwan') {
|
||||
res.json({ kwan: '孤独患者' });
|
||||
} else {
|
||||
res.send('Nooooooooooo');
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'Content-Length': '123',
|
||||
ETag: '12345'
|
||||
},
|
||||
cookies: [
|
||||
{
|
||||
name: 'myname', value: 'kwan', maxAge: 900000, httpOnly: true
|
||||
}
|
||||
],
|
||||
// 接口随机延迟
|
||||
timeout: Mock.mock({
|
||||
'number|1000-5000': 1000
|
||||
}).number
|
||||
});
|
||||
};
|
@ -1 +0,0 @@
|
||||
file test
|
@ -1,62 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const toString = Object.prototype.toString;
|
||||
module.exports = {
|
||||
|
||||
isArray(value) {
|
||||
return toString.call(value) === '[object Array]';
|
||||
},
|
||||
|
||||
isObject(value) {
|
||||
return toString.call(value) === '[object Object]';
|
||||
},
|
||||
|
||||
isFunction(value) {
|
||||
return toString.call(value) === '[object Function]';
|
||||
},
|
||||
|
||||
each(val, callback) {
|
||||
if (this.isArray(val)) {
|
||||
val.forEach(callback);
|
||||
}
|
||||
if (this.isObject(val)) {
|
||||
Object.keys(val).forEach((key) => {
|
||||
callback(val[key], key);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
watchFile(filename, callback) {
|
||||
const isWin = (process.platform === 'win32');
|
||||
if (isWin) {
|
||||
return fs.watch(filename, (event) => {
|
||||
if (event === 'change') {
|
||||
return callback(filename);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return fs.watchFile(filename, {
|
||||
interval: 200
|
||||
}, (curr, prev) => {
|
||||
if (curr.mtime > prev.mtime) {
|
||||
return callback(filename);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
},
|
||||
|
||||
unwatchFile(watcher, filename) {
|
||||
if (watcher) {
|
||||
watcher.close && watcher.close();
|
||||
} else {
|
||||
fs.unwatchFile(filename);
|
||||
}
|
||||
},
|
||||
|
||||
cleanCache(modulePath) {
|
||||
if (require.cache[modulePath]) {
|
||||
delete require.cache[modulePath];
|
||||
}
|
||||
}
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
const webpack = require('webpack');
|
||||
const log = require('../helpers/log');
|
||||
const createProdConfig = require('../configs/webpack.config');
|
||||
|
||||
const generateRoute = require('./route');
|
||||
|
||||
function startBuild(config) {
|
||||
try {
|
||||
generateRoute(config);
|
||||
const webpackConfig = createProdConfig(config, webpack, 'build');
|
||||
webpack(webpackConfig, (err) => {
|
||||
if (err) {
|
||||
log.error(err);
|
||||
return;
|
||||
}
|
||||
console.log('[build] success');
|
||||
});
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = startBuild;
|
@ -1,40 +0,0 @@
|
||||
|
||||
const webpack = require('webpack');
|
||||
const { ServiceWithBuiltIn } = require('@webank/fes-core');
|
||||
const { yParser } = require('@umijs/utils');
|
||||
const getPkg = require('../helpers/getPkg');
|
||||
const getCwd = require('../helpers/getCwd');
|
||||
const createDevServer = require('../helpers/createDevServer');
|
||||
const getPort = require('../helpers/getPort');
|
||||
const log = require('../helpers/log');
|
||||
const createDevConfig = require('../configs/webpack.config');
|
||||
|
||||
const args = yParser(process.argv.slice(2));
|
||||
|
||||
|
||||
// TODO 监听 pages 等文件变更重新编译
|
||||
async function startDev(config) {
|
||||
const service = new ServiceWithBuiltIn({
|
||||
cwd: getCwd(),
|
||||
pkg: getPkg(process.cwd())
|
||||
});
|
||||
await service.run({
|
||||
name: 'dev',
|
||||
args
|
||||
});
|
||||
|
||||
|
||||
const webpackConfig = createDevConfig(config, webpack, 'dev');
|
||||
if (!webpackConfig) return;
|
||||
|
||||
getPort(config.port)
|
||||
.then((port) => {
|
||||
log.message(`------------ find port success. port: ${port}`);
|
||||
createDevServer(port, webpackConfig);
|
||||
}).catch((err) => {
|
||||
log.message('------------ build error.');
|
||||
log.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = startDev;
|
@ -1,13 +0,0 @@
|
||||
const init = require('./init.js');
|
||||
const route = require('./route.js');
|
||||
const build = require('./build.js');
|
||||
const dev = require('./dev.js');
|
||||
const update = require('./update.js');
|
||||
|
||||
module.exports = {
|
||||
init,
|
||||
route,
|
||||
build,
|
||||
dev,
|
||||
update
|
||||
};
|
@ -1,55 +0,0 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const prompts = require('prompts');
|
||||
const tar = require('tar');
|
||||
const { execSync } = require('child_process');
|
||||
const log = require('../helpers/log');
|
||||
|
||||
|
||||
function createProject(config, projectName) {
|
||||
log.message('正在初始化项目...');
|
||||
const projectDir = path.resolve(config.folders.PROJECT_DIR, projectName);
|
||||
if (fs.pathExistsSync(projectDir)) {
|
||||
log.error('该项目已存在,请重新输入!');
|
||||
return Promise.reject();
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
const productDir = `${config.folders.PROJECT_DIR}/${projectName}`;
|
||||
const stdout = execSync('npm pack @webank/fes-template', { encoding: 'utf8', stdio: [null] });
|
||||
const filePath = path.resolve(config.folders.PROJECT_DIR, stdout.replace('\n', ''));
|
||||
fs.mkdirSync(projectDir);
|
||||
fs.createReadStream(filePath).pipe(
|
||||
tar.x({
|
||||
strip: 1,
|
||||
C: productDir // alias for cwd:'some-dir', also ok
|
||||
})
|
||||
);
|
||||
fs.unlinkSync(filePath);
|
||||
log.message(`项目 ${projectName} 创建完成,请执行下面的命令进行使用:`);
|
||||
log.message(`$ cd ${projectName}`);
|
||||
log.message('$ npm i');
|
||||
log.message('$ npm run dev');
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
async function initProject(config, projectName) {
|
||||
if (projectName) {
|
||||
await createProject(config, projectName);
|
||||
} else {
|
||||
const response = await prompts([
|
||||
{
|
||||
type: 'text',
|
||||
name: 'name',
|
||||
message: '请输入项目名称: '
|
||||
}
|
||||
]);
|
||||
if (!response.name) {
|
||||
await initProject(config, projectName);
|
||||
} else {
|
||||
await createProject(config, response.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = initProject;
|
@ -1,15 +0,0 @@
|
||||
const { exec } = require('child_process');
|
||||
const log = require('../helpers/log');
|
||||
|
||||
function update(config) {
|
||||
log.message('安装@webank/fes-core @webank/fes-ui...');
|
||||
exec(`cd ${config.folders.PROJECT_DIR} && npm i @webank/fes-core @webank/fes-ui --save && npm i`, (err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
log.message('升级完毕');
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = update;
|
@ -1,4 +1,5 @@
|
||||
|
||||
module.exports = {
|
||||
foo: () => {}
|
||||
module.exports = function () {
|
||||
// TODO 模块导出
|
||||
console.log('Hello fes');
|
||||
};
|
||||
|
@ -6,10 +6,16 @@
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"dist",
|
||||
"bin"
|
||||
],
|
||||
"bin": {
|
||||
"fes": "./bin/index.js"
|
||||
"fes": "./bin/fes.js"
|
||||
},
|
||||
"main": "index.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"author": "harrywan,qlin",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -24,88 +30,10 @@
|
||||
"strong"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.12.3",
|
||||
"@babel/helper-module-imports": "^7.12.1",
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||
"@babel/plugin-proposal-decorators": "^7.12.1",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-transform-runtime": "^7.12.1",
|
||||
"@babel/preset-env": "^7.12.1",
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"@babel/runtime-corejs3": "^7.12.1",
|
||||
"@intervolga/optimize-cssnano-plugin": "^1.0.6",
|
||||
"@soda/friendly-errors-webpack-plugin": "^1.7.1",
|
||||
"@umijs/utils": "^3.2.24",
|
||||
"@vue/babel-plugin-jsx": "^1.0.0-rc.3",
|
||||
"@vue/compiler-sfc": "^3.0.2",
|
||||
"@webank/fes-plugin-built-in": "^2.0.0",
|
||||
"@webank/fes-core": "^2.0.0",
|
||||
"@webank/fes-runtime": "^2.0.0",
|
||||
"autoprefixer": "^8.1.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"body-parser": "^1.5.2",
|
||||
"cache-loader": "^4.1.0",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.2.0",
|
||||
"chalk": "^4.1.0",
|
||||
"chokidar": "^1.7.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"commander": "^4.1.0",
|
||||
"compression-webpack-plugin": "^6.0.3",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"copy-webpack-plugin": "^5.0.4",
|
||||
"cross-spawn": "^2.1.0",
|
||||
"css-loader": "^3.1.0",
|
||||
"execa": "^0.8.0",
|
||||
"express": "^4.14.0",
|
||||
"express-http-proxy": "^0.10.0",
|
||||
"express-session": "^1.7.2",
|
||||
"faker": "^4.1.0",
|
||||
"file-loader": "^4.2.0",
|
||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||
"fs": "0.0.2",
|
||||
"fs-extra": "^8.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"http-proxy": "^1.12.0",
|
||||
"json-templater": "^1.2.0",
|
||||
"less": "^3.12.2",
|
||||
"less-loader": "^7.0.2",
|
||||
"lodash": "^4.17.4",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"morgan": "^1.2.2",
|
||||
"mustache": "^4.0.1",
|
||||
"node-plus-string": "^1.0.1",
|
||||
"normalize-path": "^1.0.0",
|
||||
"on-finished": "^2.3.0",
|
||||
"open": "^7.3.0",
|
||||
"path": "^0.12.7",
|
||||
"pkg-up": "^3.1.0",
|
||||
"postcss": "^7.0.35",
|
||||
"postcss-loader": "^4.0.4",
|
||||
"prompts": "^2.3.0",
|
||||
"request": "^2.81.0",
|
||||
"require-dir": "^0.3.0",
|
||||
"resolve": "^1.18.1",
|
||||
"shelljs": "^0.5.3",
|
||||
"string-replace-loader": "^2.2.0",
|
||||
"strip-indent": "^2.0.0",
|
||||
"style-loader": "^1.3.0",
|
||||
"stylus": "^0.54.8",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"tar": "^6.0.5",
|
||||
"tar-fs": "^1.16.0",
|
||||
"terser-webpack-plugin": "^2.2.1",
|
||||
"thread-loader": "^2.1.3",
|
||||
"url-loader": "^2.2.0",
|
||||
"vue-loader": "^16.0.0-beta.8",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-cli": "^3.3.9",
|
||||
"webpack-dev-middleware": "^3.7.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^4.2.2",
|
||||
"yargs": "^3.31.0"
|
||||
"@umijs/utils": "3.2.24",
|
||||
"resolve-cwd": "^3.0.0"
|
||||
}
|
||||
}
|
||||
|
69
packages/fes/src/cli.js
Normal file
69
packages/fes/src/cli.js
Normal file
@ -0,0 +1,69 @@
|
||||
import {
|
||||
chalk,
|
||||
yParser
|
||||
} from '@umijs/utils';
|
||||
import {
|
||||
Service
|
||||
} from './serviceWithBuiltIn';
|
||||
import fork from './utils/fork';
|
||||
import getCwd from './utils/getCwd';
|
||||
import getPkg from './utils/getPkg';
|
||||
|
||||
// process.argv: [node, fes.js, command, args]
|
||||
const args = yParser(process.argv.slice(2), {
|
||||
alias: {
|
||||
version: ['v'],
|
||||
help: ['h']
|
||||
},
|
||||
boolean: ['version']
|
||||
});
|
||||
|
||||
// TODO version 命令
|
||||
if (args.version && !args._[0]) {
|
||||
args._[0] = 'version';
|
||||
console.log(`fes@${require('../package.json').version}`);
|
||||
} else if (!args._[0]) {
|
||||
// TODO 帮助命令
|
||||
args._[0] = 'help';
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
switch (args._[0]) {
|
||||
case 'dev':
|
||||
// eslint-disable-next-line
|
||||
const child = fork({
|
||||
scriptPath: require.resolve('./forkedDev')
|
||||
});
|
||||
// ref:
|
||||
// http://nodejs.cn/api/process/signal_events.html
|
||||
process.on('SIGINT', () => {
|
||||
child.kill('SIGINT');
|
||||
process.exit(1);
|
||||
});
|
||||
process.on('SIGTERM', () => {
|
||||
child.kill('SIGTERM');
|
||||
process.exit(1);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line
|
||||
const name = args._[0];
|
||||
if (name === 'build') {
|
||||
process.env.NODE_ENV = 'production';
|
||||
}
|
||||
await new Service({
|
||||
cwd: getCwd(),
|
||||
pkg: getPkg(process.cwd())
|
||||
}).run({
|
||||
name,
|
||||
args
|
||||
});
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(chalk.red(e.message));
|
||||
console.error(e.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
48
packages/fes/src/forkedDev.js
Normal file
48
packages/fes/src/forkedDev.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { chalk, yParser } from '@umijs/utils';
|
||||
import { Service } from './serviceWithBuiltIn';
|
||||
import getCwd from './utils/getCwd';
|
||||
import getPkg from './utils/getPkg';
|
||||
|
||||
const args = yParser(process.argv.slice(2));
|
||||
|
||||
let closed = false;
|
||||
function onSignal(signal, service) {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
|
||||
// 退出时触发插件中的onExit事件
|
||||
service.applyPlugins({
|
||||
key: 'onExit',
|
||||
type: service.ApplyPluginsType.event,
|
||||
args: {
|
||||
signal
|
||||
}
|
||||
});
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
process.env.NODE_ENV = 'development';
|
||||
const service = new Service({
|
||||
cwd: getCwd(),
|
||||
pkg: getPkg(process.cwd())
|
||||
});
|
||||
await service.run({
|
||||
name: 'dev',
|
||||
args
|
||||
});
|
||||
|
||||
|
||||
// kill(2) Ctrl-C
|
||||
process.once('SIGINT', () => onSignal('SIGINT', service));
|
||||
// kill(3) Ctrl-\
|
||||
process.once('SIGQUIT', () => onSignal('SIGQUIT', service));
|
||||
// kill(15) default
|
||||
process.once('SIGTERM', () => onSignal('SIGTERM', service));
|
||||
} catch (e) {
|
||||
console.error(chalk.red(e.message));
|
||||
console.error(e.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
15
packages/fes/src/index.js
Normal file
15
packages/fes/src/index.js
Normal file
@ -0,0 +1,15 @@
|
||||
export {
|
||||
useRoute,
|
||||
useRouter,
|
||||
onBeforeRouteUpdate,
|
||||
onBeforeRouteLeave,
|
||||
RouterLink,
|
||||
useLink,
|
||||
createWebHashHistory,
|
||||
createRouter,
|
||||
Plugin,
|
||||
ApplyPluginsType
|
||||
} from '@webank/fes-runtime';
|
||||
|
||||
// @ts-ignore
|
||||
export * from '@@/core/fesExports';
|
@ -1,12 +1,12 @@
|
||||
import { dirname } from 'path';
|
||||
import CoreService from './Service/Service';
|
||||
import innerPlugins from './plugins';
|
||||
import { Service as CoreService } from '@webank/fes-core';
|
||||
import innerPlugins from '@webank/fes-plugin-built-in';
|
||||
|
||||
// TODO 迁移到 fes 目录
|
||||
class ServiceWithBuiltIn extends CoreService {
|
||||
class Service extends CoreService {
|
||||
constructor(opts) {
|
||||
process.env.FES_VERSION = require('../package').version;
|
||||
process.env.FES_DIR = dirname(require.resolve('../package'));
|
||||
process.env.UMI_DIR = dirname(require.resolve('../package'));
|
||||
|
||||
super({
|
||||
...opts,
|
||||
@ -15,4 +15,4 @@ class ServiceWithBuiltIn extends CoreService {
|
||||
}
|
||||
}
|
||||
|
||||
export default ServiceWithBuiltIn;
|
||||
export { Service };
|
60
packages/fes/src/utils/fork.js
Normal file
60
packages/fes/src/utils/fork.js
Normal file
@ -0,0 +1,60 @@
|
||||
import {
|
||||
fork
|
||||
} from 'child_process';
|
||||
|
||||
const usedPorts = [];
|
||||
let CURRENT_PORT;
|
||||
|
||||
export default function start({
|
||||
scriptPath
|
||||
}) {
|
||||
const execArgv = process.execArgv.slice(0);
|
||||
const inspectArgvIndex = execArgv.findIndex(argv => argv.includes('--inspect-brk'),);
|
||||
|
||||
if (inspectArgvIndex > -1) {
|
||||
const inspectArgv = execArgv[inspectArgvIndex];
|
||||
execArgv.splice(
|
||||
inspectArgvIndex,
|
||||
1,
|
||||
inspectArgv.replace(/--inspect-brk=(.*)/, (match, s1) => {
|
||||
let port;
|
||||
try {
|
||||
port = parseInt(s1, 10) + 1;
|
||||
} catch (e) {
|
||||
port = 9230; // node default inspect port plus 1.
|
||||
}
|
||||
if (usedPorts.includes(port)) {
|
||||
port += 1;
|
||||
}
|
||||
usedPorts.push(port);
|
||||
return `--inspect-brk=${port}`;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// set port to env when current port has value
|
||||
if (CURRENT_PORT) {
|
||||
// @ts-ignore
|
||||
process.env.PORT = CURRENT_PORT;
|
||||
}
|
||||
|
||||
const child = fork(scriptPath, process.argv.slice(2), {
|
||||
execArgv
|
||||
});
|
||||
|
||||
child.on('message', (data) => {
|
||||
const type = (data && data.type) || null;
|
||||
if (type === 'RESTART') {
|
||||
child.kill();
|
||||
start({
|
||||
scriptPath
|
||||
});
|
||||
} else if (type === 'UPDATE_PORT') {
|
||||
// set current used port
|
||||
CURRENT_PORT = data.port;
|
||||
}
|
||||
process.send && process.send(data);
|
||||
});
|
||||
|
||||
return child;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
const { join, isAbsolute } = require('path');
|
||||
import { join, isAbsolute } from 'path';
|
||||
|
||||
module.exports = () => {
|
||||
export default () => {
|
||||
const cwd = process.cwd();
|
||||
if (process.env.APP_ROOT) {
|
||||
// avoid repeat cwd path
|
@ -1,7 +1,7 @@
|
||||
const { join } = require('path');
|
||||
const getCwd = require('./getCwd');
|
||||
import { join } from 'path';
|
||||
import getCwd from './getCwd';
|
||||
|
||||
module.exports = (dir) => {
|
||||
export default (dir) => {
|
||||
try {
|
||||
// eslint-disable-next-line
|
||||
return require(join(getCwd(), 'package.json'));
|
Loading…
Reference in New Issue
Block a user