mirror of
https://gitee.com/WeBank/fes.js.git
synced 2024-11-30 02:37:52 +08:00
parent
d475753aeb
commit
3b8af8aacb
@ -36,6 +36,33 @@ API 对象是构建流程管理 Service 类的实例,api 提供一些有用的
|
||||
- **enableBy**, 是否开启插件,可配置某些场景下禁用插件。
|
||||
|
||||
|
||||
## 创建插件
|
||||
|
||||
##### 第一步:安装`create-fes-app`
|
||||
```bash
|
||||
npm i -g @fesjs/create-fes-app
|
||||
```
|
||||
|
||||
|
||||
##### 第二步:创建插件项目
|
||||
|
||||
```bash
|
||||
create-fes-app pluginName
|
||||
```
|
||||
在询问`Pick an template`时选择`Plugin`!
|
||||
|
||||
##### 第三步:进入插件目录 & 安装依赖
|
||||
```bash
|
||||
cd pluginName & yarn
|
||||
```
|
||||
|
||||
##### 第四步:启动编译
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
##### 第五步:使用插件API完成你的插件!(可以参考其他插件理解api用法和场景)
|
||||
|
||||
## 发布到 npm
|
||||
|
||||
以 `@fesjs/preset-`、`@fesjs/plugin-`、`@webank/fes-preset-`、`@webank/fes-plugin-`、`fes-preset-` 和 `fes-plugin-` 开头的依赖会被 Fes.js 自动注册为插件或插件集。
|
||||
|
26
packages/create-fes-app/src/generator/Plugin.js
Normal file
26
packages/create-fes-app/src/generator/Plugin.js
Normal file
@ -0,0 +1,26 @@
|
||||
import { Generator } from '@fesjs/utils';
|
||||
|
||||
export default class AppGenerator extends Generator {
|
||||
constructor({
|
||||
cwd, args, path, targetDir, name
|
||||
}) {
|
||||
super({
|
||||
cwd,
|
||||
args
|
||||
});
|
||||
this.path = path;
|
||||
this.targetDir = targetDir;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
async writing() {
|
||||
this.copyDirectory({
|
||||
context: {
|
||||
version: require('../../package.json').version,
|
||||
name: this.name
|
||||
},
|
||||
path: this.path,
|
||||
target: this.targetDir
|
||||
});
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import inquirer from 'inquirer';
|
||||
|
||||
import { clearConsole } from './utils';
|
||||
import AppGenerator from './generator/App';
|
||||
import PluginGenerator from './generator/Plugin';
|
||||
|
||||
export default async ({ cwd, args }) => {
|
||||
if (args.proxy) {
|
||||
@ -75,12 +76,13 @@ export default async ({ cwd, args }) => {
|
||||
choices: [
|
||||
{ name: 'PC, suitable for management desk front-end applications', value: 'pc' },
|
||||
{ name: 'H5, suitable for mobile applications', value: 'h5' },
|
||||
{ name: 'Plugin, suitable for fes plugin', value: 'plugin' },
|
||||
{ name: 'Cancel', value: false }
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
if (template) {
|
||||
if (template === 'pc' || template === 'h5') {
|
||||
const generator = new AppGenerator({
|
||||
cwd,
|
||||
args,
|
||||
@ -94,5 +96,20 @@ export default async ({ cwd, args }) => {
|
||||
console.log('$ yarn');
|
||||
console.log('$ yarn dev');
|
||||
console.log();
|
||||
} else if (template === 'plugin') {
|
||||
const generator = new PluginGenerator({
|
||||
cwd,
|
||||
args,
|
||||
targetDir,
|
||||
path: path.join(__dirname, '../templates/plugin'),
|
||||
name
|
||||
});
|
||||
await generator.run();
|
||||
console.log();
|
||||
console.log(chalk.green(`plugin ${projectName} created successfully, please execute the following command to use:`));
|
||||
console.log(`$ cd ${projectName}`);
|
||||
console.log('$ yarn');
|
||||
console.log('$ yarn dev');
|
||||
console.log();
|
||||
}
|
||||
};
|
||||
|
16
packages/create-fes-app/templates/plugin/.editorconfig
Normal file
16
packages/create-fes-app/templates/plugin/.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
lib
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
24
packages/create-fes-app/templates/plugin/.eslintrc.js
Normal file
24
packages/create-fes-app/templates/plugin/.eslintrc.js
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
module.exports = {
|
||||
extends: [
|
||||
'@webank/eslint-config-webank/vue.js'
|
||||
],
|
||||
globals: {
|
||||
// 这里填入你的项目需要的全局变量
|
||||
// 这里值为 false 表示这个全局变量不允许被重新赋值,比如:
|
||||
//
|
||||
// Vue: false
|
||||
__DEV__: false
|
||||
},
|
||||
rules: {
|
||||
'vue/comment-directive': 'off',
|
||||
'global-require': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
'no-undefined': 'off',
|
||||
'vue/valid-template-root': 'off'
|
||||
},
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
};
|
2
packages/create-fes-app/templates/plugin/.gitignore
vendored
Normal file
2
packages/create-fes-app/templates/plugin/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
lib
|
4
packages/create-fes-app/templates/plugin/.prettierrc
Normal file
4
packages/create-fes-app/templates/plugin/.prettierrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none"
|
||||
}
|
4
packages/create-fes-app/templates/plugin/build.config.js
Normal file
4
packages/create-fes-app/templates/plugin/build.config.js
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
module.exports = {
|
||||
copy: ['runtime']
|
||||
};
|
47
packages/create-fes-app/templates/plugin/package.json.tpl
Normal file
47
packages/create-fes-app/templates/plugin/package.json.tpl
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "fes-plugin-{{{name}}}",
|
||||
"version": "2.0.0",
|
||||
"description": "一个fes.js插件",
|
||||
"main": "lib/index.js",
|
||||
"files": [
|
||||
"lib",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "node scripts/build.js --watch",
|
||||
"build": "node scripts/build.js",
|
||||
"lint": "eslint -c ./.eslintrc.js --ext .js,.jsx,.vue,.ts"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
],
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@webank/eslint-config-webank": "0.3.1",
|
||||
"chalk": "^4.1.2",
|
||||
"chokidar": "^3.5.2",
|
||||
"deepmerge": "^4.2.2",
|
||||
"fs-extra": "^10.0.0",
|
||||
"husky": "^4.3.0",
|
||||
"lint-staged": "^10.4.0",
|
||||
"yargs-parser": "^20.2.9"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fesjs/fes": "^2.0.0",
|
||||
"vue": "^3.0.5"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,vue,ts}": [
|
||||
"eslint --format=codeframe"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged",
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
}
|
||||
}
|
145
packages/create-fes-app/templates/plugin/scripts/build.js
Normal file
145
packages/create-fes-app/templates/plugin/scripts/build.js
Normal file
@ -0,0 +1,145 @@
|
||||
// 关闭 import 规则
|
||||
/* eslint import/no-extraneous-dependencies: 0 */
|
||||
|
||||
const fs = require('fs');
|
||||
const fse = require('fs-extra');
|
||||
const path = require('path');
|
||||
const merge = require('deepmerge');
|
||||
const chokidar = require('chokidar');
|
||||
const chalk = require('chalk');
|
||||
const argv = require('yargs-parser')(process.argv.slice(2));
|
||||
|
||||
const compiler = require('./compiler');
|
||||
const randomColor = require('./randomColor');
|
||||
const pkg = require('../package.json');
|
||||
|
||||
|
||||
const ESM_OUTPUT_DIR = 'es';
|
||||
const NODE_CJS_OUTPUT_DIR = 'lib';
|
||||
const SOURCE_DIR = 'src';
|
||||
const CONFIG_FILE_NAME = 'build.config.js';
|
||||
const GLOBAL_CONFIG_PATH = path.join(process.cwd(), CONFIG_FILE_NAME);
|
||||
const DEFAULT_CONFIG = {
|
||||
target: 'node'
|
||||
};
|
||||
|
||||
function genLog(pkgName) {
|
||||
return (msg) => {
|
||||
console.log(`${randomColor(pkgName)}: ${msg}`);
|
||||
};
|
||||
}
|
||||
|
||||
function genShortPath(filePath) {
|
||||
const codePath = filePath.split(`/${SOURCE_DIR}/`)[1];
|
||||
return `${SOURCE_DIR}/${codePath}`;
|
||||
}
|
||||
|
||||
function getPkgSourcePath() {
|
||||
return path.join(process.cwd(), SOURCE_DIR);
|
||||
}
|
||||
|
||||
function getOutputPath(config) {
|
||||
if (config.target === 'browser') {
|
||||
return path.join(process.cwd(), ESM_OUTPUT_DIR);
|
||||
}
|
||||
|
||||
return path.join(process.cwd(), NODE_CJS_OUTPUT_DIR);
|
||||
}
|
||||
|
||||
function getGlobalConfig() {
|
||||
if (fs.existsSync(GLOBAL_CONFIG_PATH)) {
|
||||
const userConfig = require(GLOBAL_CONFIG_PATH);
|
||||
return merge(DEFAULT_CONFIG, userConfig);
|
||||
}
|
||||
return DEFAULT_CONFIG;
|
||||
}
|
||||
|
||||
function cleanBeforeCompilerResult(log) {
|
||||
const esmOutputDir = path.join(process.cwd(), ESM_OUTPUT_DIR);
|
||||
const cjsOutputDir = path.join(process.cwd(), NODE_CJS_OUTPUT_DIR);
|
||||
if (fs.existsSync(esmOutputDir)) {
|
||||
log(chalk.gray(`Clean ${ESM_OUTPUT_DIR} directory`));
|
||||
fse.removeSync(esmOutputDir);
|
||||
}
|
||||
if (fs.existsSync(cjsOutputDir)) {
|
||||
log(chalk.gray(`Clean ${NODE_CJS_OUTPUT_DIR} directory`));
|
||||
fse.removeSync(cjsOutputDir);
|
||||
}
|
||||
}
|
||||
|
||||
function transformFile(filePath, outputPath, config, log) {
|
||||
if (/\.[jt]sx?$/.test(path.extname(filePath))) {
|
||||
try {
|
||||
const code = fs.readFileSync(filePath, 'utf-8');
|
||||
const shortFilePath = genShortPath(filePath);
|
||||
const transformedCode = compiler(code, config);
|
||||
|
||||
const type = config.target === 'browser' ? ESM_OUTPUT_DIR : NODE_CJS_OUTPUT_DIR;
|
||||
log(`Transform to ${type} for ${config.target === 'browser' ? chalk.yellow(shortFilePath) : chalk.blue(shortFilePath)}`);
|
||||
fse.outputFileSync(outputPath, transformedCode);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
fse.copySync(filePath, outputPath);
|
||||
}
|
||||
}
|
||||
|
||||
function compilerPkg(codeDir, outputDir, config, log) {
|
||||
const files = fs.readdirSync(codeDir);
|
||||
files.forEach((file) => {
|
||||
const filePath = path.join(codeDir, file);
|
||||
const outputFilePath = path.join(outputDir, file);
|
||||
const fileStats = fs.lstatSync(filePath);
|
||||
if (config.copy.includes(file)) {
|
||||
fse.copySync(filePath, outputFilePath);
|
||||
} else if (fileStats.isDirectory(filePath) && !/__tests__/.test(file)) {
|
||||
fse.ensureDirSync(outputFilePath);
|
||||
compilerPkg(filePath, outputFilePath, config, log);
|
||||
} else if (fileStats.isFile(filePath)) {
|
||||
transformFile(filePath, outputFilePath, config, log);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function watchFile(dir, outputDir, config, log) {
|
||||
chokidar
|
||||
.watch(dir, {
|
||||
ignoreInitial: true
|
||||
})
|
||||
.on('all', (event, changeFile) => {
|
||||
// 修改的可能是一个目录,一个文件,一个需要 copy 的文件 or 目录
|
||||
const shortChangeFile = genShortPath(changeFile);
|
||||
const outputPath = changeFile.replace(dir, outputDir);
|
||||
const stat = fs.lstatSync(changeFile);
|
||||
log(`[${event}] ${shortChangeFile}`);
|
||||
if (config.resolveCopy.some(item => changeFile.startsWith(item))) {
|
||||
fse.copySync(changeFile, outputPath);
|
||||
} else if (stat.isFile()) {
|
||||
transformFile(changeFile, outputPath, config, log);
|
||||
} else if (stat.isDirectory()) {
|
||||
compilerPkg(changeFile, outputPath, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function main() {
|
||||
const sourceCodeDir = getPkgSourcePath();
|
||||
const pkgName = pkg.name;
|
||||
if (fs.existsSync(sourceCodeDir)) {
|
||||
const log = genLog(pkgName);
|
||||
const config = getGlobalConfig();
|
||||
const outputDir = getOutputPath(config);
|
||||
|
||||
cleanBeforeCompilerResult(log);
|
||||
const type = config.target === 'browser' ? ESM_OUTPUT_DIR : NODE_CJS_OUTPUT_DIR;
|
||||
log(chalk.white(`Build ${type} with babel`));
|
||||
compilerPkg(sourceCodeDir, outputDir, config, log);
|
||||
if (argv.watch) {
|
||||
log(chalk.magenta(`Start watch ${SOURCE_DIR} directory...`));
|
||||
watchFile(sourceCodeDir, outputDir, config, log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
48
packages/create-fes-app/templates/plugin/scripts/compiler.js
Normal file
48
packages/create-fes-app/templates/plugin/scripts/compiler.js
Normal file
@ -0,0 +1,48 @@
|
||||
// 关闭 import 规则
|
||||
/* eslint import/no-extraneous-dependencies: 0 */
|
||||
|
||||
const babel = require('@babel/core');
|
||||
|
||||
|
||||
function transform(code, options) {
|
||||
const result = babel.transformSync(code, options);
|
||||
return result.code;
|
||||
}
|
||||
|
||||
function transformNodeCode(code) {
|
||||
return transform(code, {
|
||||
presets: [
|
||||
['@babel/preset-env', {
|
||||
modules: 'cjs',
|
||||
targets: { node: '12' }
|
||||
}]
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function transformBrowserCode(code) {
|
||||
// 因为 fes.js 在生产打包的时候,会处理所有的 node_modules 下的文件,确保不会丢失必要 polyfill
|
||||
// 因此这里不对 polyfill 进行处理,避免全局污染
|
||||
return transform(code, {
|
||||
presets: [
|
||||
['@babel/preset-env', {
|
||||
modules: false,
|
||||
useBuiltIns: false,
|
||||
targets: { chrome: '51' }
|
||||
}]
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function compiler(code, config) {
|
||||
if (!config.target || config.target === 'node') {
|
||||
return transformNodeCode(code);
|
||||
}
|
||||
if (config.target === 'browser') {
|
||||
return transformBrowserCode(code);
|
||||
}
|
||||
throw new Error(`config target error: ${config.target}, only can use 'node' and 'browser'`);
|
||||
}
|
||||
|
||||
|
||||
module.exports = compiler;
|
@ -0,0 +1,35 @@
|
||||
/* eslint import/no-extraneous-dependencies: 0 */
|
||||
const chalk = require('chalk');
|
||||
|
||||
const colors = [
|
||||
'red',
|
||||
'green',
|
||||
'yellow',
|
||||
'blue',
|
||||
'magenta',
|
||||
'cyan',
|
||||
'gray',
|
||||
'redBright',
|
||||
'greenBright',
|
||||
'yellowBright',
|
||||
'blueBright',
|
||||
'magentaBright',
|
||||
'cyanBright'
|
||||
];
|
||||
|
||||
let index = 0;
|
||||
const cache = {};
|
||||
|
||||
module.exports = function (pkg) {
|
||||
if (!cache[pkg]) {
|
||||
const color = colors[index];
|
||||
const str = chalk[color].bold(pkg);
|
||||
cache[pkg] = str;
|
||||
if (index === colors.length - 1) {
|
||||
index = 0;
|
||||
} else {
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
return cache[pkg];
|
||||
};
|
45
packages/create-fes-app/templates/plugin/src/index.js.tpl
Normal file
45
packages/create-fes-app/templates/plugin/src/index.js.tpl
Normal file
@ -0,0 +1,45 @@
|
||||
import { join } from 'path';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
const namespace = 'plugin-{{{name}}}';
|
||||
|
||||
export default (api) => {
|
||||
api.describe({
|
||||
key: '{{{name}}}',
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.object();
|
||||
},
|
||||
default: {}
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
utils: { Mustache }
|
||||
} = api;
|
||||
|
||||
const absoluteFilePath = join(namespace, 'core.js');
|
||||
|
||||
const absRuntimeFilePath = join(namespace, 'runtime.js');
|
||||
|
||||
api.onGenerateFiles(() => {
|
||||
// 运行时执行的代码全部copy到临时目录,此时不需要编译,稍后webpack会编译临时目录代码
|
||||
api.copyTmpFiles({
|
||||
namespace,
|
||||
path: join(__dirname, 'runtime'),
|
||||
ignore: ['.tpl']
|
||||
});
|
||||
|
||||
// 有些运行时代码通过配置生成,则通过tpl写入
|
||||
api.writeTmpFile({
|
||||
path: absoluteFilePath,
|
||||
content: Mustache.render(
|
||||
readFileSync(join(__dirname, 'runtime/core.tpl'), 'utf-8'),
|
||||
{
|
||||
}
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
api.addRuntimePlugin(() => `@@/${absRuntimeFilePath}`);
|
||||
};
|
@ -0,0 +1 @@
|
||||
// 通过配置生成的代码
|
@ -0,0 +1,5 @@
|
||||
// 配置运行时插件
|
||||
|
||||
export function onAppCreated({ app }) {
|
||||
console.log(app);
|
||||
}
|
Loading…
Reference in New Issue
Block a user