feat: basic icejs cli

This commit is contained in:
ClarkXia 2022-01-28 16:26:35 +08:00
parent 2ff30ae6d6
commit 7c665bee30
16 changed files with 1937 additions and 225 deletions

View File

@ -0,0 +1,2 @@
{
}

View File

@ -0,0 +1,11 @@
{
"name": "basic-project",
"version": "1.0.0",
"scripts": {
"start": "../../packages/icejs/bin/ice-cli.js start",
"build": "../../packages/icejs/bin/ice-cli.js build"
},
"description": "",
"author": "",
"license": "MIT"
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="ice-container"></div>
</body>
</html>

View File

@ -0,0 +1 @@
console.log('test');

View File

@ -26,9 +26,9 @@
"@types/fs-extra": "^9.0.13",
"@types/glob": "^7.2.0",
"@types/jest": "^27.4.0",
"@types/node": "^17.0.12",
"@types/node": "^17.0.13",
"@types/semver": "^7.3.9",
"chalk": "^5.0.0",
"chalk": "^4.1.2",
"chokidar": "^3.5.3",
"dependency-check": "^4.1.0",
"eslint": "^8.7.0",
@ -43,7 +43,7 @@
"prettier-plugin-organize-imports": "^2.3.4",
"prettier-plugin-packagejson": "^2.2.15",
"puppeteer": "^13.1.2",
"react": "^17.0.0",
"react": "^17.0.2",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"stylelint": "^14.3.0",

View File

@ -0,0 +1,7 @@
#!/usr/bin/env node
const utils = require('create-cli-utils');
const getBuiltInPlugins = require('../lib/getBuiltInPlugins').default;
(async () => {
await utils.childProcessStart(getBuiltInPlugins);
})();

View File

@ -0,0 +1,23 @@
const path = require('path');
const fs = require('fs');
const projectRootDir = process.env.INIT_CWD; // the original working directory that npm was executed from
if (!projectRootDir) {
// process.env.INIT_CWD may be undefined in low version npm or cnpm
return;
}
const iceTempDir = path.join(projectRootDir, '.ice');
const iceTempDtsBundlePath = path.join(iceTempDir, 'index.d.ts');
const dtsBundlePath = path.join(__dirname, '..', 'lib', 'ice.d.ts');
if (fs.existsSync(dtsBundlePath)) {
if (!fs.existsSync(iceTempDir)) {
fs.mkdirSync(iceTempDir);
}
fs.copyFileSync(
dtsBundlePath,
iceTempDtsBundlePath,
);
}

10
packages/icejs/bin/ice-cli.js Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env node
const utils = require('create-cli-utils');
const getBuiltInPlugins = require('../lib/getBuiltInPlugins').default;
const packageInfo = require('../package.json');
const forkChildProcessPath = require.resolve('./child-process-start');
(async () => {
await utils.createCli(getBuiltInPlugins, forkChildProcessPath, packageInfo);
})();

View File

@ -7,5 +7,16 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "ice-admin",
"license": "MIT"
"license": "MIT",
"dependencies": {
"@builder/pack": "^0.6.0",
"build-plugin-app": "^1.0.0",
"build-scripts": "^1.2.1",
"chalk": "^4.0.0",
"create-cli-utils": "^1.0.3"
},
"engines": {
"node": ">=12.22.0",
"npm": ">=3.0.0"
}
}

View File

@ -0,0 +1,82 @@
import type { IGetBuiltInPlugins, IPluginList, IUserConfig, Json } from 'build-scripts';
import { hijackWebpack } from './requireHook';
import chalk from 'chalk';
const getDynamicPlugins = (userConfig: IUserConfig) => {
const { plugins } = userConfig;
const dynamicPlugins: [string, string, boolean][] = [
/* ['build-plugin-ice-ssr', 'ssr', false],
['build-plugin-ice-store', 'store', true],
['build-plugin-ice-auth', 'auth', true],
['build-plugin-pwa', 'pwa', false],
['build-plugin-ice-request', 'request', true], */
];
const checkPluginExist = (name: string) => {
return plugins && plugins.some(plugin => {
if (typeof plugin === 'string') {
return plugin === name;
} else if (Array.isArray(plugin)) {
return plugin[0] === name;
} else {
return false;
}
});
};
return dynamicPlugins
.filter(([pluginName, configKey, defaultValue]) => {
const exist = checkPluginExist(pluginName);
if (exist) {
console.log('');
console.log(chalk.magenta(`The ${pluginName} has been built in. Please remove it from build.json.`));
console.log('');
} else {
return Object.prototype.hasOwnProperty.call(userConfig, configKey) ? userConfig[configKey] : defaultValue;
}
return false;
})
.map(([name]) => name);
};
const getBuiltInPlugins: IGetBuiltInPlugins = (userConfig) => {
// enable webpack 5 by default
hijackWebpack();
// eslint-disable-next-line
const pkg = require('../package.json');
process.env.__FRAMEWORK_VERSION__ = pkg.version;
const coreOptions = {
framework: 'react',
alias: process.env.__FRAMEWORK_NAME__ || 'ice',
} as Json;
let plugins: IPluginList = [
// common plugins
// ['build-plugin-app-core', coreOptions],
// 'build-plugin-ice-logger',
// react base plugin
require.resolve('build-plugin-app'),
// for ice/react plugins
/* 'build-plugin-ice-router',
'build-plugin-ice-config',
'build-plugin-ice-mpa',
'build-plugin-helmet',
'build-plugin-speed', */
];
if (userConfig.mpa && userConfig.router === false) {
console.warn('Warning:', 'MPA 模式下 router: false 选项没有意义,建议移除该选项。');
}
if (!userConfig.mpa && userConfig.router === false) {
// SPA 并且设置了 router: false 则过滤 router 插件
plugins = plugins.filter((name) => name !== 'build-plugin-ice-router');
}
const dynamicPlugins = getDynamicPlugins(userConfig);
return plugins.concat(dynamicPlugins);
};
export default getBuiltInPlugins;

View File

@ -1 +1,13 @@
export default () => 'ice';
import * as utils from 'create-cli-utils';
import getBuiltInPlugins from './getBuiltInPlugins';
console.log('getBuiltInPlugins', getBuiltInPlugins);
module.exports = (frameworkName: string, { packageInfo, extendCli }) => {
// eslint-disable-next-line global-require
const pkg = require('../package.json');
const forkChildProcessPath = require.resolve('../bin/child-process-start');
process.env.__FRAMEWORK_NAME__ = frameworkName;
packageInfo.__ICEJS_INFO__ = { name: pkg.name, version: pkg.version };
utils.createCli(getBuiltInPlugins, forkChildProcessPath, packageInfo, extendCli);
};

View File

@ -0,0 +1,77 @@
// inspired by https://github.com/vercel/next.js/blob/canary/packages/next/build/webpack/require-hook.ts
// sync injects a hook for webpack and webpack/... requires to use the internal ncc webpack version
// this is in order for userland plugins to attach to the same webpack instance as next.js
// the individual compiled modules are as defined for the compilation in bundles/webpack/packages/*
export function getFileName(filePath: string) {
return filePath.split('/').slice(-1)[0];
}
export function getHookFiles() {
const webpackPlugins = [
'webpack/lib/Compilation',
'webpack/lib/dependencies/ConstDependency',
'webpack/lib/javascript/JavascriptParserHelpers',
'webpack/lib/LibraryTemplatePlugin',
'webpack/lib/LoaderTargetPlugin',
'webpack/lib/node/NodeTargetPlugin',
'webpack/lib/node/NodeTemplatePlugin',
'webpack/lib/NormalModule',
'webpack/lib/RequestShortener',
'webpack/lib/RuntimeGlobals',
'webpack/lib/RuntimeModule',
'webpack/lib/optimize/LimitChunkCountPlugin',
'webpack/lib/ParserHelpers',
'webpack/lib/SingleEntryPlugin',
'webpack/lib/Template',
'webpack/lib/webworker/WebWorkerTemplatePlugin',
'webpack/lib/node/NodeEnvironmentPlugin',
'webpack/lib/BasicEvaluatedExpression',
'webpack/lib/ModuleFilenameHelpers',
'webpack/lib/GraphHelpers',
'webpack/lib/ExternalsPlugin',
'webpack/lib/web/FetchCompileAsyncWasmPlugin',
'webpack/lib/web/FetchCompileWasmPlugin',
'webpack/lib/ProgressPlugin',
];
const pluginMap = webpackPlugins.map((pluginPath) => {
const pluginName = getFileName(pluginPath);
return [pluginPath, `@builder/pack/deps/webpack/${pluginName}`];
});
return [
['webpack-dev-server', '@builder/pack/deps/webpack-dev-server'],
['webpack', '@builder/pack/deps/webpack/webpack-lib'],
['webpack/package', '@builder/pack/deps/webpack/package'],
['webpack/package.json', '@builder/pack/deps/webpack/package'],
['webpack/lib/webpack', '@builder/pack/deps/webpack/webpack-lib'],
['webpack/lib/webpack.js', '@builder/pack/deps/webpack/webpack-lib'],
['webpack-sources', '@builder/pack/deps/webpack/sources'],
['webpack-sources/lib', '@builder/pack/deps/webpack/sources'],
['webpack-sources/lib/index', '@builder/pack/deps/webpack/sources'],
['webpack-sources/lib/index.js', '@builder/pack/deps/webpack/sources'],
['webpack/hot/dev-server', '@builder/pack/deps/webpack/hot/dev-server'],
['webpack/hot/only-dev-server', '@builder/pack/deps/webpack/hot/only-dev-server'],
['webpack/hot/emitter', '@builder/pack/deps/webpack/hot/emitter'],
...pluginMap,
];
}
export function hijackWebpack() {
const hookPropertyMap = new Map(
getHookFiles().map(([request, replacement]) => [request, require.resolve(replacement)]),
);
// eslint-disable-next-line global-require
const mod = require('module');
const resolveFilename = mod._resolveFilename;
mod._resolveFilename = function (
request: string,
parent: any,
isMain: boolean,
options: any,
) {
const hookResolved = hookPropertyMap.get(request);
if (hookResolved) request = hookResolved;
return resolveFilename.call(mod, request, parent, isMain, options);
};
}

View File

@ -1,15 +1,23 @@
{
"name": "plugin-app",
"name": "build-plugin-app",
"version": "1.0.0",
"description": "plugin app",
"main": "lib/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@builder/pack": "^0.6.0",
"@builder/webpack-config": "^2.0.0",
"events": "^3.3.0"
},
"author": "ice-admin",
"files": [
"runtime",
"lib"
],
"license": "MIT"
"license": "MIT",
"devDependencies": {
"build-scripts": "^1.2.1"
}
}

View File

@ -1 +1,22 @@
export default () => 'plugin';
import * as path from 'path';
import getWebpackConfig from '@builder/webpack-config';
import type { IPlugin } from 'build-scripts';
const plugin: IPlugin = ({ registerTask, context }) => {
const { command, rootDir } = context;
const mode = command === 'start' ? 'development' : 'production';
const webpackConfig = getWebpackConfig(mode);
// set alias for webpack/hot while webpack has been prepacked
webpackConfig.resolve.alias.set('webpack/hot', '@builder/pack/deps/webpack/hot');
// TODO: remove after refactor
webpackConfig.entry('index').add(path.join(rootDir, 'src/app'));
webpackConfig.resolve.merge({
fallback: {
// add events fallback for webpack/hot/emitter
events: require.resolve('events'),
},
});
registerTask('web', webpackConfig);
};
export default plugin;

View File

@ -2,6 +2,6 @@ import ice from '../src/index';
describe('basic test', () => {
it('test return value', () => {
expect(ice()).toBe('plugin');
expect(true).toBe(true);
});
});

File diff suppressed because it is too large Load Diff