fix: refactor error handling (#6286)

* fix: refactor error handling

* fix: logger

* fix: sourcemap

* fix: test case

* fix: test

* fix: add brief error message

* fix: changelog and lint
This commit is contained in:
ClarkXia 2023-06-08 18:12:12 +08:00 committed by GitHub
parent abdd49de8d
commit c70c77379d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 347 additions and 348 deletions

View File

@ -0,0 +1,5 @@
---
'@ice/app': patch
---
fix: refactor error handling

View File

@ -0,0 +1,5 @@
---
'@ice/webpack-config': patch
---
fix: refactor error handling

View File

@ -0,0 +1,5 @@
---
'@ice/bundles': patch
---
fix: bump webpack(5.84.1) and webpack-dev-server(4.15.0)

View File

@ -24,6 +24,6 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.2",
"speed-measure-webpack-plugin": "^1.5.0",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
}
}

View File

@ -23,6 +23,6 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.2",
"speed-measure-webpack-plugin": "^1.5.0",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
}
}

View File

@ -21,6 +21,6 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"speed-measure-webpack-plugin": "^1.5.0",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
}
}

View File

@ -24,6 +24,6 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.2",
"speed-measure-webpack-plugin": "^1.5.0",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
}
}

View File

@ -28,6 +28,6 @@
"@types/react-dom": "^18.0.2",
"browserslist": "^4.19.3",
"speed-measure-webpack-plugin": "^1.5.0",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
}
}

View File

@ -24,6 +24,6 @@
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.2",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
}
}

View File

@ -26,6 +26,6 @@
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.2",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
}
}

View File

@ -19,6 +19,6 @@
"fs-extra": "^10.0.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.2",
"webpack": "^5.73.0"
"webpack": "^5.84.1"
}
}

View File

@ -19,6 +19,6 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"speed-measure-webpack-plugin": "^1.5.0",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
}
}

View File

@ -19,6 +19,6 @@
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.2",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
}
}

View File

@ -65,9 +65,9 @@
"terser-webpack-plugin": "5.3.5",
"typescript": "^4.6.4",
"trusted-cert": "1.1.3",
"webpack": "5.80.0",
"webpack": "5.84.1",
"webpack-bundle-analyzer": "4.5.0",
"webpack-dev-server": "4.11.1",
"webpack-dev-server": "4.15.0",
"unplugin": "0.9.5",
"bonjour-service": "^1.0.13",
"colorette": "^2.0.10",

View File

@ -82,7 +82,7 @@
"react-router": "6.11.2",
"sass": "^1.50.0",
"unplugin": "^0.9.0",
"webpack": "^5.80.0",
"webpack": "^5.84.1",
"webpack-dev-server": "^4.7.4"
},
"peerDependencies": {

View File

@ -145,9 +145,9 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
delete require.cache[serverEntry];
return await dynamicImport(serverEntry, true);
}
} catch (err) {
} catch (error) {
// make error clearly, notice typeof err === 'string'
logger.error('Excute server entry error:', err);
logger.error('Execute server entry error:', error);
return;
}
}
@ -397,9 +397,9 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
spinner: buildSpinner,
});
}
} catch (err) {
} catch (error) {
buildSpinner.stop();
throw err;
throw error;
}
},
};

View File

@ -64,7 +64,9 @@ const scanPlugin = (options: Options): Plugin => {
pkgNameCache.set(resolved, result);
return result;
} catch (err) {
logger.error(`cant resolve package of path: ${resolved}`, err);
logger.error(`Can't resolve package of path: ${resolved}`);
// Scan error doesn't affect the build process.
logger.error(err);
}
};

View File

@ -1,6 +1,4 @@
import path from 'path';
import type { TransformOptions } from 'esbuild';
import { esbuild } from '@ice/bundles';
import MagicString from '@ice/bundles/compiled/magic-string/index.js';
import esModuleLexer from '@ice/bundles/compiled/es-module-lexer/index.js';
import type { ImportSpecifier } from '@ice/bundles/compiled/es-module-lexer/index.js';
@ -29,17 +27,10 @@ const transformImportPlugin = (preBundleDepsMetadata: PreBundleDepsMetaData, ser
transformInclude(id: string) {
return /\.(js|jsx|ts|tsx)$/.test(id);
},
async transform(source: string, id: string) {
async transform(source: string) {
await init;
let imports: readonly ImportSpecifier[] = [];
// es-module-lexer do not support parse jsx syntax, so we first transform the source by esbuild.
const transformed = await transformWithESBuild(
source,
id,
);
source = transformed.code;
imports = parse(transformed.code)[0];
imports = parse(source)[0];
const str = new MagicString(source);
for (let index = 0; index < imports.length; index++) {
const {
@ -61,30 +52,4 @@ const transformImportPlugin = (preBundleDepsMetadata: PreBundleDepsMetaData, ser
};
};
// Fork from https://github.com/vitejs/vite/blob/d98c8a710b8f0804120c05e5bd3eb403f17e7b30/packages/vite/src/node/plugins/esbuild.ts#L60
async function transformWithESBuild(
input: string,
filePath: string,
options: TransformOptions = {},
) {
let loader = options?.loader as TransformOptions['loader'];
if (!loader) {
const extname = path.extname(filePath).slice(1);
if (extname === 'mjs' || extname === 'cjs' || extname === 'js') {
loader = 'jsx';
} else {
loader = extname as TransformOptions['loader'];
}
}
const transformOptions = {
sourcemap: true,
sourcefile: filePath,
...options,
loader,
} as TransformOptions;
return await esbuild.transform(input, transformOptions);
}
export default transformImportPlugin;

View File

@ -36,19 +36,17 @@ function guessLoader(id: string): Loader {
* but esbuild needs them, we fix the two methods.
*/
export function fixSourceMap(map: any) {
if (!('toString' in map)) {
Object.defineProperty(map, 'toString', {
Object.defineProperty(map, 'toMapString', {
enumerable: false,
value: function toString() {
return JSON.stringify(this);
},
});
}
if (!('toUrl' in map)) {
Object.defineProperty(map, 'toUrl', {
enumerable: false,
value: function toUrl() {
return `data:application/json;charset=utf-8;base64,${Buffer.from(this.toString()).toString('base64')}`;
return `data:application/json;charset=utf-8;base64,${Buffer.from(this.toMapString()).toString('base64')}`;
},
});
}
@ -135,13 +133,15 @@ const transformPipe = (options: PluginOptions = {}): Plugin => {
sourceCode = result;
} else if (typeof result === 'object' && result !== null) {
sourceCode = result.code;
sourceMap = result.map;
sourceMap = typeof result.map === 'string' ? JSON.parse(result.map) : result.map;
}
}
if (sourceMap && typeof sourceMap !== 'string') {
if (!sourceMap.sourcesContent || sourceMap.sourcesContent.length === 0) {
sourceMap.sourcesContent = [sourceCode];
}
// Use relative path to make sure the source map is correct.
sourceMap.sources = [path.relative(resolveDir, id)];
sourceMap = fixSourceMap(sourceMap);
sourceCode += `\n//# sourceMappingURL=${sourceMap.toUrl()}`;
}

View File

@ -83,12 +83,12 @@ function decodeParam(val: any) {
}
try {
return decodeURIComponent(val);
} catch (err) {
if (err instanceof URIError) {
err.message = `Failed to decode param ' ${val} '`;
(err as any).status = 400;
(err as any).statusCode = 400;
} catch (error) {
if (error instanceof URIError) {
error.message = `Failed to decode param ' ${val} '`;
(error as any).status = 400;
(error as any).statusCode = 400;
}
throw err;
throw error;
}
}

View File

@ -47,7 +47,8 @@ export default function getConfigs(rootDir: string, exclude: string[] = []): Moc
try {
mockModule = require(mockFile);
} catch (error) {
logger.error(`Failed to parse mock file ${mockFile}.\n${error.message}`);
logger.error(`Failed to parse mock file ${mockFile}`);
logger.error(error);
return;
}
const config = mockModule.default || mockModule || {};

View File

@ -135,7 +135,7 @@ export async function analyzeImports(files: string[], options: Options) {
})();
}));
} catch (err) {
logger.error('[ERROR]', `optimize runtime failed when analyze ${filePath}`);
logger.briefError(`Optimize runtime failed when analyze ${filePath}`);
logger.debug(err);
throw err;
}
@ -151,8 +151,7 @@ export async function analyzeImports(files: string[], options: Options) {
}));
}
return importSet;
} catch (err) {
logger.debug(err);
} catch (_) {
return false;
}
}
@ -199,8 +198,8 @@ export async function scanImports(entries: string[], options?: ScanOptions) {
);
logger.debug(`Scan completed in ${(performance.now() - start).toFixed(2)}ms:`, deps);
} catch (error) {
logger.error('Failed to scan module imports.', `\n${error.message}`);
logger.debug(error.stack);
logger.briefError('Failed to scan module imports.');
logger.debug(error);
}
return orderedDependencies(deps);
}
@ -256,8 +255,8 @@ export async function getFileExports(options: FileOptions): Promise<CachedRouteE
}
}
} catch (error) {
logger.error(`Failed to get route ${filePath} exports.`, `\n${error.message}`);
logger.debug(error.stack);
logger.briefError(`Failed to get route ${filePath} exports.`);
logger.debug(error);
cached = {
exports: [],
hash: fileHash,

View File

@ -135,11 +135,8 @@ export const getAppExportConfig = (rootDir: string) => {
transformInclude: (id) => id.includes('src/app') || id.includes('.ice'),
getOutfile,
needRecompile: async (entry, keepExports) => {
let cached = null;
const cachedKey = `app_${keepExports.join('_')}_${process.env.__ICE_VERSION__}`;
try {
cached = await getCache(rootDir, cachedKey);
} catch (err) { }
const cached = await getCache(rootDir, cachedKey);
const fileHash = await getFileHash(appEntry);
if (!cached || fileHash !== cached) {
await setCache(rootDir, cachedKey, fileHash);
@ -153,8 +150,8 @@ export const getAppExportConfig = (rootDir: string) => {
try {
return (await config.getConfig(exportNames || ['default', 'defineAppConfig'])) || {};
} catch (error) {
logger.warn('Failed to get app config.', `\n${error.message}`);
logger.debug(error.stack);
logger.briefError('Failed to get app config.');
logger.debug(error);
}
};
@ -163,8 +160,8 @@ export const getAppExportConfig = (rootDir: string) => {
try {
config.setCompiler(serverCompiler);
} catch (error) {
logger.error('Failed to compile app config.', `\n${error.message}`);
logger.debug(error.stack);
logger.briefError('Failed to compile app config.');
logger.debug(error);
}
},
getAppConfig,
@ -203,15 +200,12 @@ export const getRouteExportConfig = (rootDir: string) => {
rootDir,
getOutfile: getRouteConfigOutfile,
needRecompile: async (entry) => {
let cached = false;
try {
cached = await getCache(rootDir, cachedKey);
} catch (err) { }
const cached = await getCache(rootDir, cachedKey);
if (cached) {
// Always use cached file path while `routes-config` trigger re-compile by webpack plugin.
return entry;
} else {
setCache(rootDir, cachedKey, 'true');
await setCache(rootDir, cachedKey, 'true');
return false;
}
},
@ -222,16 +216,13 @@ export const getRouteExportConfig = (rootDir: string) => {
rootDir,
getOutfile: getdataLoadersConfigOutfile,
needRecompile: async (entry) => {
let cached = false;
const cachedKey = `loader_config_file_${process.env.__ICE_VERSION__}`;
try {
cached = await getCache(rootDir, cachedKey);
} catch (err) { }
const cached = await getCache(rootDir, cachedKey);
if (cached) {
// Always use cached file path while `routes-config` trigger re-compile by webpack plugin.
return entry;
} else {
setCache(rootDir, cachedKey, 'true');
await setCache(rootDir, cachedKey, 'true');
return false;
}
},
@ -259,7 +250,7 @@ export const getRouteExportConfig = (rootDir: string) => {
const ensureRoutesConfig = async () => {
const configFile = await routeConfig.getConfigFile(['pageConfig']);
if (!configFile) {
setCache(rootDir, cachedKey, '');
await setCache(rootDir, cachedKey, '');
}
};
@ -269,14 +260,14 @@ export const getRouteExportConfig = (rootDir: string) => {
try {
routeConfig.setCompiler(serverCompiler);
} catch (error) {
routeConfigLogger.error('Failed to get route config.', `\n${error.message}`);
routeConfigLogger.debug(error.stack);
routeConfigLogger.briefError('Failed to get route config.');
routeConfigLogger.debug(error);
}
try {
dataloaderConfig.setCompiler(serverCompiler);
} catch (error) {
dataLoaderConfigLogger.error('Failed to get dataLoader config.', `\n${error.message}`);
dataLoaderConfigLogger.debug(error.stack);
dataLoaderConfigLogger.briefError('Failed to get dataLoader config.');
dataLoaderConfigLogger.debug(error);
}
},
getRoutesConfig,

View File

@ -111,7 +111,7 @@ export default async function preBundleDeps(
metadata,
};
} catch (error) {
logger.error('Failed to bundle dependencies.');
logger.briefError('Failed to bundle dependencies.');
logger.debug(error);
return {};
}

View File

@ -128,8 +128,7 @@ export function createServerCompiler(options: Options) {
return (source: string, id: string) => {
return {
...getConfig(source, id),
// Force inline when use swc as a transformer.
sourceMaps: sourceMap && 'inline',
sourceMaps: !!sourceMap,
};
};
}
@ -254,13 +253,11 @@ export function createServerCompiler(options: Options) {
serverEntry,
};
} catch (error) {
logger.error(
logger.briefError(
'Server compiled with errors.',
`\nEntryPoints: ${JSON.stringify(buildOptions.entryPoints)}`,
`\n${error.message}`,
);
// TODO: Log esbuild options with namespace.
// logger.debug('esbuild options: ', buildOptions);
logger.debug(error.stack);
return {
error: error as Error,

View File

@ -136,7 +136,7 @@ async function webpackCompiler(options: {
const firstWebpackConfig = webpackConfigs[0];
firstWebpackConfig.plugins.push((compiler: webpack.Compiler) => {
compiler.hooks.beforeCompile.tap('spinner', () => {
spinner.text = 'compiling...\n';
spinner.text = 'Compiling...\n';
});
compiler.hooks.afterEmit.tap('spinner', () => {
spinner.stop();
@ -146,9 +146,9 @@ async function webpackCompiler(options: {
try {
// @ts-ignore
compiler = webpackBundler(webpackConfigs);
} catch (err) {
} catch (error) {
logger.error('Webpack compile error.');
logger.error(err.message || err);
logger.error(error);
}
let isFirstCompile = true;

View File

@ -39,9 +39,9 @@ export default async function generateEntry(options: Options): Promise<EntryResu
let serverEntry;
try {
serverEntry = await dynamicImport(entry);
} catch (err) {
// make error clearly, notice typeof err === 'string'
throw new Error(`import ${entry} error: ${err}`);
} catch (error) {
logger.error(`Error occurred while importing ${entry}`);
throw error;
}
// When enable hash-router, only generate one html(index.html).

View File

@ -1,8 +1,9 @@
import type { Consola } from 'consola';
import type { Consola, ConsolaLogObject } from 'consola';
import consola from 'consola';
// In ice.js, we use DEBUG_TAG instead of DEBUG to avoid other libs which use `DEBUG` as their flag log debug info.
const { DEBUG_TAG } = process.env;
// eslint-disable-next-line camelcase
const { DEBUG_TAG, npm_lifecycle_event } = process.env;
function getEnableAndDisabledNamespaces(namespaces?: string) {
const enabledNamespaces: RegExp[] = [];
@ -52,22 +53,37 @@ export type CreateLoggerReturnType = Pick<Consola, |
'ready' |
'debug' |
'trace'
>;
> & { briefError?: (message: ConsolaLogObject | any, ...args: any[]) => void };
export type CreateLogger = (namespace?: ICELogNamespace) => CreateLoggerReturnType;
export const createLogger: CreateLogger = (namespace) => {
function briefError(message: ConsolaLogObject | any, ...args: any[]) {
consola.error(message, ...args);
if (!DEBUG_TAG) {
// eslint-disable-next-line camelcase
consola.log(`run \`DEBUG_TAG=${namespace || '*'} npm run ${npm_lifecycle_event || 'start'}\` to view error details`);
}
}
function extendLoggerInstance(instance: Consola): CreateLoggerReturnType {
const logger = {} as CreateLoggerReturnType;
['fatal', 'error', 'warn', 'log', 'info', 'start', 'success', 'ready', 'debug', 'trace'].forEach((method) => {
logger[method] = instance[method];
});
logger.briefError = briefError;
return logger;
}
if (DEBUG_TAG) {
consola.level = 4;
}
if (!namespace) {
return consola;
return extendLoggerInstance(consola);
}
if (DEBUG_TAG) {
if (enabled(namespace)) {
return consola.withTag(namespace);
return extendLoggerInstance(consola.withTag(namespace));
} else {
return {
fatal() { },
@ -80,10 +96,11 @@ export const createLogger: CreateLogger = (namespace) => {
ready() { },
debug() { },
trace() { },
briefError() {},
};
}
} else {
return consola.withTag(namespace);
return extendLoggerInstance(consola.withTag(namespace));
}
};

View File

@ -3,12 +3,18 @@ import cacache from '@ice/bundles/compiled/cacache/index.js';
const CACHE_PATH = 'node_modules/.cache/route';
export function getCache(rootDir: string, id: string) {
export async function getCache(rootDir: string, id: string) {
const cachePath = path.join(rootDir, CACHE_PATH);
return cacache.get(cachePath, id).then((cache) => JSON.parse(cache.data.toString('utf-8')));
try {
return await cacache.get(cachePath, id)
.then((cache) => JSON.parse(cache.data.toString('utf-8')));
} catch (_) {
// Ignore get cache error.
return null;
}
}
export function setCache(rootDir: string, id: string, data: any) {
export async function setCache(rootDir: string, id: string, data: any) {
const cachePath = path.join(rootDir, CACHE_PATH);
return cacache.put(cachePath, id, JSON.stringify(data));
return await cacache.put(cachePath, id, JSON.stringify(data));
}

View File

@ -0,0 +1,5 @@
import { defineAppConfig } from '@ice/runtime';
import { getAppConfig } from '@ice/runtime/client';
console.log(getAppConfig);
export default defineAppConfig({});

View File

@ -13,7 +13,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
const alias = { '@': path.join(__dirname, './fixtures/scan') };
const rootDir = path.join(__dirname, './fixtures/scan');
const cacheDir = path.join(rootDir, '.cache');
const appEntry = path.join(__dirname, './fixtures/scan/app.ts');
const appEntry = path.join(__dirname, './fixtures/scan/import.js');
const outdir = path.join(rootDir, 'build');
it('transform module import', async () => {
@ -35,7 +35,7 @@ it('transform module import', async () => {
transformImportPlugin(),
],
});
const buildContent = await fse.readFile(path.join(outdir, 'app.js'));
const buildContent = await fse.readFile(path.join(outdir, 'import.js'), 'utf-8');
expect(buildContent.includes('../../.cache/deps/@ice_runtime_client.mjs')).toBeTruthy();
expect(buildContent.includes('../../.cache/deps/@ice_runtime.mjs')).toBeTruthy();
});

View File

@ -21,7 +21,7 @@
"@ice/bundles": "^0.1.10"
},
"devDependencies": {
"webpack": "^5.80.0"
"webpack": "^5.84.1"
},
"publishConfig": {
"access": "public"

View File

@ -49,7 +49,7 @@
"devDependencies": {
"@ice/app": "^3.2.0",
"@ice/runtime": "^1.2.0",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
},
"repository": {
"type": "http",

View File

@ -28,7 +28,7 @@
"@ice/app": "^3.2.0",
"build-scripts": "^2.1.1-0",
"esbuild": "^0.17.16",
"webpack": "^5.80.0",
"webpack": "^5.84.1",
"webpack-dev-server": "^4.9.2"
},
"repository": {

View File

@ -62,7 +62,7 @@ export default async function generateManifest({
// dataLoader may have side effect code.
dataloaderConfig = await getDataloaderConfig();
} catch (err) {
logger.debug('GetDataloaderConfig failed.');
logger.briefError('GetDataloaderConfig failed.');
logger.debug(err);
}

View File

@ -38,7 +38,7 @@ const createPHAMiddleware = ({
// dataLoader may have side effect code.
dataloaderConfig = await getDataloaderConfig();
} catch (err) {
logger.debug('GetDataloaderConfig failed.');
logger.briefError('GetDataloaderConfig failed.');
logger.debug(err);
}

View File

@ -31,7 +31,7 @@
},
"devDependencies": {
"@ice/app": "^3.2.1",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
},
"repository": {
"type": "http",

View File

@ -26,7 +26,7 @@
"devDependencies": {
"esbuild": "^0.17.16",
"postcss": "^8.4.18",
"webpack": "^5.80.0",
"webpack": "^5.84.1",
"webpack-dev-server": "^4.7.4"
},
"scripts": {

View File

@ -94,8 +94,9 @@ export async function redirectImport(code: string, options: Options): Promise<st
let imports: readonly ImportSpecifier[] = [];
try {
imports = parse(code)[0];
} catch (e) {
consola.debug('[parse error]', e);
} catch (error) {
consola.error('Parse error when redirect import.');
consola.error(error);
}
if (!imports.length) {
return code;

View File

@ -16,7 +16,7 @@
},
"devDependencies": {
"@ice/webpack-config": "^1.0.0",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
},
"scripts": {
"watch": "tsc -w",

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,7 @@
"glob": "^7.2.3",
"gray-matter": "^4.0.3",
"typescript": "^4.9.5",
"webpack": "^5.80.0"
"webpack": "^5.84.1"
},
"browserslist": {
"production": [