@ali-i18n-fe/dada-component
Version:
191 lines (157 loc) • 5.04 kB
JavaScript
const {
getProgram
} = require("@ali-i18n-fe/react-docgen-typescript-loader-add-tag/dist/loader");
const ts = require("typescript");
const path = require("path");
const fs = require("fs");
const { buildPublicPath, getPreviewPathByFilePath } = require("../utils");
const currentPath = process.cwd();
module.exports = function loader(source) {
// Loaders can operate in either synchronous or asynchronous mode. Errors in
// asynchronous mode should be reported using the supplied callback.
// Will return a callback if operating in asynchronous mode.
const callback = this.async();
try {
const newSource = processResource.call(this, source);
if (!callback) return newSource;
callback(null, newSource);
return;
} catch (e) {
if (callback) {
callback(e);
return;
}
throw e;
}
};
function processResource(source) {
const context = this;
const isProduction = context.mode === "production";
const filePath = context.resourcePath;
let previewFile, previewPic;
if (filePath) {
// only for docs exist
previewFile = path.resolve(filePath, "..", "docs.tsx");
if (!fs.existsSync(previewFile)) {
return source;
}
const srcPic = path.resolve(filePath, "..", "preview.png");
const distPic = srcPic.replace(
path.resolve(currentPath + "/src/"),
path.resolve(currentPath + "/dist/docs/")
);
if (fs.existsSync(srcPic)) {
previewPic = srcPic;
} else if (isProduction && fs.existsSync(distPic)) {
previewPic = distPic;
}
}
context.cacheable(true);
const program = getProgram(path.resolve(currentPath, "src"));
const sourceFile = program.getSourceFile(filePath);
if (!sourceFile) {
return source;
}
sourceFile.hasBeenIncrementallyParsed = false;
const newSourceFile = ts.updateSourceFile(sourceFile, source, {
span: { start: 0, length: sourceFile.text.length },
newLength: source.length
});
const checker = program.getTypeChecker();
ts.bindSourceFile(newSourceFile, {});
const moduleSymbol = checker.getSymbolAtLocation(newSourceFile);
const exportsModule = checker.getExportsOfModule(moduleSymbol);
if (!exportsModule) {
return source;
}
const funMaps = exportsModule
.map(exportModel => {
const { declarations } = exportModel;
const statement = declarations.find(
statement =>
statement.kind === ts.SyntaxKind.ClassDeclaration ||
statement.kind === ts.SyntaxKind.ExportAssignment ||
statement.kind === ts.SyntaxKind.FunctionDeclaration
);
if (statement) {
const name = statement.name || statement.expression || {};
if (!name.text) {
return null;
}
const { pos, end } = statement || {};
return { name: name.text, start: pos, end };
}
})
.filter(Boolean);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const printNode = sourceNode =>
printer.printNode(ts.EmitHint.Unspecified, sourceNode, sourceFile);
const relativeFilename = buildPublicPath(context, previewFile);
const properties = [
{ key: "__docsPath", value: relativeFilename },
{ key: "displayName", value: "$1" },
{ key: "__displayName", value: "$1" }
];
previewPic &&
properties.push({
key: "__previewPath",
value: getPreviewPathByFilePath(context, previewPic)
});
const blocks = funMaps.map(funMap => {
const { name, end } = funMap;
const codeBlocks = ts.createTry(
ts.createBlock(createStatement(name, properties), true),
ts.createCatchClause(
ts.createVariableDeclaration(ts.createIdentifier("__docsPath_error")),
ts.createBlock([])
),
undefined
);
return [printNode(codeBlocks), end];
});
return addBlocks(source, blocks);
}
function addBlocks(source, blocks) {
let addCount = 0;
let result = source;
blocks.forEach(block => {
const [content, pos] = block;
const insertPos = pos + addCount;
result = result.slice(0, insertPos) + content + result.slice(insertPos);
addCount += content.length;
});
return result;
}
function createStatement(funName, properties) {
return properties.map(({ key, value }) =>
insertTsIgnoreBeforeStatement(
ts.createStatement(
ts.createBinary(
ts.createPropertyAccess(
ts.createIdentifier(funName),
ts.createIdentifier(key)
),
ts.SyntaxKind.EqualsToken,
ts.createLiteral(funName.replace(/(.+)/, value))
)
)
)
);
}
function createStatements(funNames, properties) {
return funNames.reduce(
(prev, funName) => prev.concat(createStatement(funName, properties)),
[]
);
}
function insertTsIgnoreBeforeStatement(statement) {
ts.setSyntheticLeadingComments(statement, [
{
text: " @ts-ignore", // Leading space is important here
kind: ts.SyntaxKind.SingleLineCommentTrivia,
pos: -1,
end: -1
}
]);
return statement;
}