@typed/content-hash
Version:
Content hash a directory of HTML/JS/CSS files and other static assets
161 lines • 8.19 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createJavascriptPlugin = void 0;
const tslib_1 = require("tslib");
const E = (0, tslib_1.__importStar)(require("@typed/fp/Env"));
const FxEnv_1 = require("@typed/fp/FxEnv");
const builtin_modules_1 = (0, tslib_1.__importDefault)(require("builtin-modules"));
const function_1 = require("fp-ts/function");
const Option_1 = require("fp-ts/Option");
const ReadonlyArray_1 = require("fp-ts/ReadonlyArray");
const path_1 = require("path");
const ts_morph_1 = require("ts-morph");
const typed_colors_1 = require("typed-colors");
const typescript_1 = require("typescript");
const logging_1 = require("../../application/services/logging");
const dependencyEq_1 = require("../dependencyEq");
const ensureRelative_1 = require("../ensureRelative");
const fsReadFile_1 = require("../fsReadFile");
const getHashFor_1 = require("../hashes/getHashFor");
const defaults_1 = require("./defaults");
const getFileExtension_1 = require("./getFileExtension");
const resolvePathFromSourceFile_1 = require("./resolvePathFromSourceFile");
const resolveTsConfigPaths_1 = require("./resolveTsConfigPaths");
const specifiersToSkip = [...builtin_modules_1.default, 'tslib'];
const multiSeparatedExtensions = ['.proxy.js', '.d.ts.map', '.js.map', '.d.ts'];
const simpleExtensions = ['.js', '.ts', '.jsx', '.tsx'];
const supportedExtensions = [...multiSeparatedExtensions, ...simpleExtensions];
const quotes = [`'`, `"`];
const stripSpecifier = (s) => stripPrefix(stripPostfix(s));
const stripPrefix = (s) => (quotes.includes(s[0]) ? s.slice(1) : s);
const stripPostfix = (s) => {
const length = s.length;
const lastIndex = length - 1;
const end = s[lastIndex];
if (quotes.includes(end)) {
return s.slice(0, lastIndex);
}
return s;
};
const EXTENSIONLESS_EXTENSIONS = {
'.js': ['.js', '.jsx'],
'.jsx': ['.jsx', '.js'],
'.d.ts': ['.d.ts', '.ts', '.js'],
};
const getExtensions = (extension) => {
if (extension in EXTENSIONLESS_EXTENSIONS) {
return EXTENSIONLESS_EXTENSIONS[extension];
}
return [extension];
};
const getProxyReplacementExt = (ext) => {
const base = ext.slice(0, -9);
if (base.endsWith('.map')) {
return base.slice(0, -4);
}
return base;
};
const createJavascriptPlugin = (options) => {
const { compilerOptions = (0, typescript_1.getDefaultCompilerOptions)(), mainFields = defaults_1.MAIN_FIELDS } = options;
const pathsResolver = (0, resolveTsConfigPaths_1.createResolveTsConfigPaths)({ compilerOptions });
const project = new ts_morph_1.Project({
compilerOptions: {
...compilerOptions,
allowJs: true,
},
skipAddingFilesFromTsConfig: true,
skipFileDependencyResolution: true,
useInMemoryFileSystem: true,
});
const javascript = {
readFilePath: (filePath) => (0, FxEnv_1.Do)(function* (_) {
const ext = (0, getFileExtension_1.getFileExtension)(filePath);
if (!supportedExtensions.some((se) => ext.endsWith(se))) {
yield* _((0, logging_1.debug)(`${(0, typed_colors_1.red)(`[JS]`)} Unsupported file extension ${filePath}`));
return Option_1.none;
}
const shouldUseHashFor = multiSeparatedExtensions.some((se) => ext.endsWith(se));
const isProxyJs = ext.endsWith('.proxy.js');
yield* _((0, logging_1.debug)(`${(0, typed_colors_1.yellow)(`[JS]`)} Reading ${filePath}...`));
const initial = yield* _((0, fsReadFile_1.fsReadFile)(filePath, { supportsSourceMaps: !isProxyJs, isBase64Encoded: false }));
const document = shouldUseHashFor
? (0, getHashFor_1.getHashFor)(initial, isProxyJs ? getProxyReplacementExt(ext) : '.js')
: initial;
yield* _((0, logging_1.debug)(`${(0, typed_colors_1.yellow)(`[JS]`)} Finding dependencies ${filePath}...`));
return (0, Option_1.some)(yield* _(findDependencies(project, pathsResolver, mainFields, document)));
}),
};
return javascript;
};
exports.createJavascriptPlugin = createJavascriptPlugin;
function findDependencies(project, pathsResolver, mainFields, document) {
const contents = document.contents;
const sourceFile = project.getSourceFile(document.filePath) || project.createSourceFile(document.filePath, contents);
const sourceFilePath = sourceFile.getFilePath();
const extension = (0, getFileExtension_1.getFileExtension)(sourceFilePath);
const hasServiceWorkerRegister = contents.includes('serviceWorker.register');
const standardStringLiterals = [
...sourceFile.getImportStringLiterals(),
...sourceFile
.getExportDeclarations()
.map((d) => d.getModuleSpecifier())
.filter((x) => x !== undefined),
...(hasServiceWorkerRegister ? findServiceWorkerRegister(sourceFile) : []),
];
const absoluteStringLiterals = [
...(extension.endsWith('.proxy.js')
? sourceFile.getExportAssignments().flatMap((a) => a.getDescendantsOfKind(ts_morph_1.SyntaxKind.StringLiteral))
: []),
];
const stringLiterals = [
...standardStringLiterals.map((s) => [s, false]),
...absoluteStringLiterals.map((s) => [s, true]),
];
return (0, function_1.pipe)(stringLiterals.map(([literal, useBaseName]) => {
const specifier = stripSpecifier(literal.getText());
const moduleSpecifier = useBaseName ? (0, ensureRelative_1.ensureRelative)(path_1.posix.basename(specifier)) : specifier;
if (specifiersToSkip.includes(moduleSpecifier)) {
return E.of(Option_1.none);
}
return (0, function_1.pipe)((0, resolvePathFromSourceFile_1.resolvePathFromSourceFile)({
moduleSpecifier,
directory: path_1.posix.dirname(sourceFilePath),
pathsResolver,
extensions: getExtensions(extension),
mainFields,
}), E.map((0, Option_1.map)((filePath) => {
const start = literal.getStart() + 1;
const end = literal.getEnd() - 1;
const dep = {
specifier,
filePath,
fileExtension: (0, getFileExtension_1.getFileExtension)(filePath),
position: { start, end },
};
return dep;
})));
}), E.zip, E.map((dependencies) => ({
...document,
dependencies: (0, function_1.pipe)(dependencies.filter(Option_1.isSome).map((o) => o.value), (0, ReadonlyArray_1.uniq)(dependencyEq_1.dependencyEq)),
})));
}
function findServiceWorkerRegister(sourceFile) {
const literals = [];
const callExpressions = sourceFile.getStatements().flatMap((s) => s.getChildrenOfKind(ts_morph_1.SyntaxKind.CallExpression));
callExpressions.forEach((callExpression) => {
var _a, _b, _c;
const firstAccess = callExpression.getFirstDescendantByKind(ts_morph_1.SyntaxKind.PropertyAccessExpression);
const secondaryAccess = firstAccess === null || firstAccess === void 0 ? void 0 : firstAccess.getFirstDescendantByKind(ts_morph_1.SyntaxKind.PropertyAccessExpression);
const firstIdentifier = (_a = firstAccess === null || firstAccess === void 0 ? void 0 : firstAccess.getLastChildByKind(ts_morph_1.SyntaxKind.Identifier)) === null || _a === void 0 ? void 0 : _a.getText();
const secondaryIdentifier = secondaryAccess
? (_b = secondaryAccess.getLastChildByKind(ts_morph_1.SyntaxKind.Identifier)) === null || _b === void 0 ? void 0 : _b.getText()
: (_c = firstAccess === null || firstAccess === void 0 ? void 0 : firstAccess.getFirstChildByKind(ts_morph_1.SyntaxKind.Identifier)) === null || _c === void 0 ? void 0 : _c.getText();
if (firstIdentifier === 'register' && secondaryIdentifier === 'serviceWorker') {
literals.push(...callExpression
.getChildrenOfKind(ts_morph_1.SyntaxKind.SyntaxList)
.flatMap((l) => l.getChildrenOfKind(ts_morph_1.SyntaxKind.StringLiteral)));
}
});
return literals;
}
//# sourceMappingURL=javascript.js.map