@apployees-nx/webserver
Version:
A create-react-app inspired plugin for Nx, with SSR and PWA capabilities.
192 lines (163 loc) • 5.91 kB
text/typescript
/*******************************************************************************
* © Apployees Inc., 2019
* All Rights Reserved.
******************************************************************************/
import * as ts from "typescript";
import { join as pathJoin, sep } from "path";
import { IImportTransformerOptions } from "./import-transformer-options";
import _ from "lodash";
export interface IImportedStruct {
importName: string;
variableName?: string;
}
function join(...params: string[]) {
/* istanbul ignore if */
if (sep === "\\") {
const ret = pathJoin(...params);
return ret.replace(/\\/g, "/");
}
/* istanbul ignore next */
return pathJoin(...params);
}
// camel2Dash camel2Underline
// borrow from https://github.com/ant-design/babel-plugin-import
function camel2Dash(_str: string) {
const str = _str[0].toLowerCase() + _str.substr(1);
return str.replace(/([A-Z])/g, ($1) => `-${$1.toLowerCase()}`);
}
function camel2Underline(_str: string) {
const str = _str[0].toLowerCase() + _str.substr(1);
return str.replace(/([A-Z])/g, ($1) => `_${$1.toLowerCase()}`);
}
function getImportedStructs(node: ts.Node) {
const structs = new Set<IImportedStruct>();
node.forEachChild((importChild) => {
if (!ts.isImportClause(importChild)) {
return;
}
// not allow default import, or mixed default and named import
// e.g. import foo from 'bar'
// e.g. import foo, { bar as baz } from 'x'
// and must namedBindings exist
if (importChild.name || !importChild.namedBindings) {
return;
}
// not allow namespace import
// e.g. import * as _ from 'lodash'
if (!ts.isNamedImports(importChild.namedBindings)) {
return;
}
importChild.namedBindings.forEachChild((namedBinding) => {
// ts.NamedImports.elements will always be ts.ImportSpecifier
const importSpecifier = namedBinding as ts.ImportSpecifier;
// import { foo } from 'bar'
if (!importSpecifier.propertyName) {
structs.add({ importName: importSpecifier.name.text });
return;
}
// import { foo as bar } from 'baz'
structs.add({
importName: importSpecifier.propertyName.text,
variableName: importSpecifier.name.text,
});
});
});
return structs;
}
function createDistAst(struct: IImportedStruct, options: IImportTransformerOptions) {
const astNodes: ts.Node[] = [];
const { libraryName } = options;
const _importName = struct.importName;
const importName = options.transformCamel2Underline
? camel2Underline(_importName)
: options.transformCamel2Dash
? camel2Dash(_importName)
: _importName;
const subdirectory = join(options.subdirectory || "", importName);
const importPath = join(libraryName!, subdirectory);
try {
const scriptNode = ts.createImportDeclaration(
undefined,
undefined,
ts.createImportClause(
struct.variableName || !options.useDefaultImports ? undefined : ts.createIdentifier(struct.importName),
struct.variableName
? ts.createNamedImports([
ts.createImportSpecifier(
options.useDefaultImports ? ts.createIdentifier("default") : ts.createIdentifier(struct.importName),
ts.createIdentifier(struct.variableName),
),
])
: options.useDefaultImports
? undefined
: ts.createNamedImports([ts.createImportSpecifier(undefined, ts.createIdentifier(struct.importName))]),
),
ts.createLiteral(importPath),
);
astNodes.push(scriptNode);
const additionalImportsContainer = options.additionalImports || {};
let additionalImports = additionalImportsContainer[importName];
// we don't want to do x || y || z because the array may be intentionally empty.
if (_.isNil(additionalImports)) {
additionalImports = additionalImportsContainer[_importName];
if (_.isNil(additionalImports)) {
additionalImports = additionalImportsContainer["*"];
}
}
if (additionalImports && additionalImports.length > 0) {
for (const additionalImport of additionalImports) {
const additionalImportPath = `${importPath}/${additionalImport}`;
if (additionalImportPath) {
const styleNode = ts.createImportDeclaration(
undefined,
undefined,
undefined,
ts.createLiteral(additionalImportPath),
);
astNodes.push(styleNode);
}
}
}
// tslint:disable-next-line:no-empty
} catch (e) {
astNodes.push(
ts.createImportDeclaration(
undefined,
undefined,
ts.createImportClause(
undefined,
ts.createNamedImports([ts.createImportSpecifier(undefined, ts.createIdentifier(_importName))]),
),
ts.createLiteral(libraryName!),
),
);
}
return astNodes;
}
export function importTransformer(_options: Array<IImportTransformerOptions>) {
const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
const visitor: ts.Visitor = (node) => {
if (ts.isSourceFile(node)) {
return ts.visitEachChild(node, visitor, context);
}
if (!ts.isImportDeclaration(node)) {
return node;
}
const importedLibName = (node.moduleSpecifier as ts.StringLiteral).text;
const options = _options.find((something) => something.libraryName === importedLibName);
if (!options) {
return node;
}
const structs = getImportedStructs(node);
if (structs.size === 0) {
return node;
}
return Array.from(structs).reduce((acc, struct) => {
const nodes = createDistAst(struct, options);
return acc.concat(nodes);
}, [] as ts.Node[]);
};
return (node) => ts.visitNode(node, visitor);
};
return transformer;
}