Skip to content

Commit

Permalink
feat(compile): add logger
Browse files Browse the repository at this point in the history
  • Loading branch information
lc-soft committed Oct 5, 2023
1 parent 8f47fdb commit 6f6fba2
Show file tree
Hide file tree
Showing 9 changed files with 685 additions and 345 deletions.
23 changes: 23 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = {
env: {
node: true,
es2021: true,
},
extends: "eslint:recommended",
overrides: [
{
env: {
node: true,
},
files: [".eslintrc.{js,cjs}"],
parserOptions: {
sourceType: "script",
},
},
],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
rules: {},
};
21 changes: 0 additions & 21 deletions .eslintrc.js

This file was deleted.

179 changes: 118 additions & 61 deletions lib/compiler/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fs from "fs-extra";
import path from "path";
import winston from "winston";
import compilerConfig from "./config.js";
import CSSLoader from "./css-loader.js";
import FileLoader from "./file-loader.js";
Expand All @@ -20,13 +21,6 @@ const loaderMap = {
"ts-loader": TsLoader,
};

class ModuleResolveError extends Error {
constructor(msg) {
super(msg);
this.name = "ModuleResolveError";
}
}

function getDirs() {
const rootContext = process.cwd();
const appDirPath = path.join(rootContext, "app");
Expand Down Expand Up @@ -69,7 +63,7 @@ function resolveLoaders(config) {
options = item.options || {};
}
if (!loader) {
throw new Error(`invalid loader configuration: ${JSON.stringify(item)}`);
throw new Error(`Invalid loader configuration: ${JSON.stringify(item)}`);
}
return { loader, options };
});
Expand All @@ -95,7 +89,7 @@ function isNodeModulePath(name) {
/**
* 确定模块路径
* @param {string} name
* @param {ImporterContext} context
* @param {CompilerContext} context
*/
function resolveModulePath(name, context) {
// TODO: 支持 alias 配置路径别名
Expand All @@ -118,23 +112,61 @@ function resolveModulePath(name, context) {
)
);
}
throw new ModuleResolveError(`${name}: File does not exist`);
throw new Error(`${name}: File does not exist`);
}

function printError(resourcePath, error) {
console.error(`Error in: ${resourcePath}:`);
console.error(error);
function createLogger(logFile, verbose) {
const levelKey = Symbol.for("level");
const fmt = winston.format;
const logFormatter = fmt.printf((info) => {
if (info[levelKey] === "info" || info[levelKey] === "debug") {
return info.message;
}
return `${info.level}: ${info.message}`;
});
return winston.createLogger({
level: verbose ? "debug" : "info",
transports: [
new winston.transports.Console({
level: verbose ? "debug" : "info",
format: fmt.combine(fmt.colorize(), logFormatter),
}),
new winston.transports.File({
filename: logFile,
format: logFormatter,
}),
],
});
}

/**
* @param {string} file 文件或目录路径
* @param {CompilerOptions} compilerOptions 编译选项
*/
export default async function compile(file, compilerOptions) {
/** @type {CompilerOptions} */
const options = {
...getDirs(),
...compilerOptions,
};
const logFile = path.join(options.buildDirPath, "compile.log");
const logger = createLogger(logFile, options.verbose);

/** @type {ModuleCacheMap} */
const mdouleCacheMap = {};

/**
* @param {string} resourcePath
* @param {string | Error} error
*/
function printError(resourcePath, error) {
logger.error(
`in ${resourcePath}:\n${
error instanceof Error ? `${error.message}\n${error.stack}` : error
}`
);
}

function useModuleCache(modulePath, context) {
const outputPath = resolveModulePath(modulePath, context);
const outputDirPath = path.dirname(outputPath);
Expand Down Expand Up @@ -175,33 +207,87 @@ export default async function compile(file, compilerOptions) {
}
}

/**
* 加载模块
* @param {string} resourcePath
* @param {LoaderRule[]} loaders
* @param {CompilerContext} context
*/
async function loadModule(resourcePath, loaders, context) {
return loaders.reduceRight(async (inputPromise, config) => {
const input = await inputPromise;
try {
return await (function LOADER_EXECUTION() {
return config.loader.call(
{
...context,
getOptions() {
return config.options;
},
},
input
);
})();
} catch (err) {
context.emitError(
`ModuleLoaderError (from ${config.loader.name}): ${err.message}\n${err.stack}`
);
err.isReported = true;
throw err;
}
}, Promise.resolve(fs.readFileSync(resourcePath)));
}

/**
* 加载模块
* @param {string} resourcePath
* @param {LoaderRule[]} loaders
* @param {CompilerContext} context
*/
async function importModule(resourcePath, loaders, context) {
const resolvedPath = isNodeModulePath(resourcePath)
? resourcePath
: path.resolve(context.context, resourcePath);
const cache = useModuleCache(resolvedPath, context);
if (cache.state === "pending") {
cache.state = "loading";
if (loaders.length > 0) {
try {
await compileModule(resolvedPath, loaders, context);
cache.resolve(await import(`file://${cache.outputPath}`));
} catch (err) {
err.message = `in ${resolvedPath}:\n${err.message}`;
context.emitError(err);
cache.reject(err);
}
} else {
cache.resolve({ default: null, metadata: { type: "javascript" } });

if (cache.state !== "pending") {
return cache.exports;
}
cache.state = "loading";
if (loaders.length < 1) {
cache.resolve({ default: null, metadata: { type: "javascript" } });
return cache.exports;
}
try {
context.logger.info(
`Compling ${path.relative(context.rootContext, resourcePath)}`
);
const result = await loadModule(resourcePath, loaders, context);
if (result !== undefined) {
context.logger.debug(
`Generating ${path.relative(
context.rootContext,
context.resourceOutputPath
)}`
);
fs.writeFileSync(context.resourceOutputPath, result);
}
cache.resolve(await import(`file://${cache.outputPath}`));
} catch (err) {
if (!err.isReported) {
context.emitError(err);
}
cache.reject(err);
throw err;
}
return cache.exports;
}

function createImporterContext(resourcePath) {
/** @type {ImporterContext} */
function createCompilerContext(resourcePath) {
/** @type {CompilerContext} */
const context = {
...options,
logger,
resourcePath,
resourceOutputPath: `${resourcePath}.h`,
context: path.dirname(resourcePath),
Expand Down Expand Up @@ -239,38 +325,6 @@ export default async function compile(file, compilerOptions) {
return resolveLoaders(matchedRule.use);
}

/**
* 编译模块
* @param {string} resourcePath
* @param {LoaderRule[]} loaders
* @param {ImporterContext} context
*/
async function compileModule(resourcePath, loaders, context) {
const result = await loaders.reduceRight(async (inputPromise, config) => {
const input = await inputPromise;
try {
return await (function LOADER_EXECUTION() {
return config.loader.call(
{
...context,
getOptions() {
return config.options;
},
},
input
);
})();
} catch (err) {
err.message = `(from ${config.loader.name}): ${err.message}`;
context.emitError(err);
throw err;
}
}, Promise.resolve(fs.readFileSync(resourcePath)));
if (result !== undefined) {
fs.writeFileSync(context.resourceOutputPath, result);
}
}

async function compileFile(filePath) {
if (fs.statSync(filePath).isDirectory()) {
return Promise.all(
Expand All @@ -281,7 +335,7 @@ export default async function compile(file, compilerOptions) {
}
const loaders = matchLoaders(filePath);
if (loaders.length > 0) {
await importModule(filePath, loaders, createImporterContext(filePath));
await importModule(filePath, loaders, createCompilerContext(filePath));
}
}

Expand All @@ -290,7 +344,10 @@ export default async function compile(file, compilerOptions) {
}
try {
await compileFile(path.resolve(file));
logger.info("Compilation completed!");
} catch (err) {
logger.error("Compilation failed!");
logger.error(`For more details, please refer to the log file: ${logFile}`);
throw new Error("Compilation failed!");
}
}
Loading

0 comments on commit 6f6fba2

Please sign in to comment.