blob: 740572e382e9cc25b699a89def1594176d63bb2d [file]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @ts-check
import fs from 'node:fs';
import path from 'node:path';
/**
* `path.dirname` does not include trailing slashes. If we would always
* use `path.dirname` and then later perform comparisons on the paths that
* it returns, we could run into false positives. For example, given the
* the following two paths:
*
* front_end/timeline_model/TimelineModel.js
* front_end/timeline/Timeline.js
*
* And that would have the following values for `path.dirname`:
*
* front_end/timeline_model
* front_end/timeline
*
* If we would do a simple `.startswith` on the `path.dirname` of both of
* these paths, then the first path would start with the dirname of the
* second. However, they *are* part of different folders. To fix that problem,
* we need to force a path separator after each folder. That makes sure we
* and up with the following comparison of path dirnames:
*
* front_end/timeline_model/
* front_end/timeline/
*
* Now, the first path does *not* start with the second one, as expected.
*
* @param file
* @returns
*/
export function dirnameWithSeparator(file) {
return path.dirname(file) + path.sep;
}
export function devtoolsPlugin(source, importer, externalFiles, root, genRoot, bundleAll = false) {
if (!importer) {
return null;
}
if (!bundleAll && (!externalFiles || !(externalFiles instanceof Set))) {
throw new Error('devtoolsPlugin requires an externalFiles Set');
}
if (!root || typeof root !== 'string') {
throw new Error('devtoolsPlugin requires a root path');
}
if (!genRoot || typeof genRoot !== 'string') {
throw new Error('devtoolsPlugin requires a genRoot path');
}
if (!bundleAll) {
if (source === '../../lib/codemirror' || !source.startsWith('.')) {
// These are imported via require(...), but we don't use
// @rollup/plugin-commonjs. So this check is not necessary for rollup. But
// need to have this for esbuild as it doesn't ignore require(...).
return {
id: source,
external: true,
};
}
} else if (!source.startsWith('.')) {
return null;
}
const importedFilelocation = path.normalize(
path.join(path.dirname(importer), source),
);
let rel = importedFilelocation;
if (path.isAbsolute(importedFilelocation)) {
if (importedFilelocation === genRoot || importedFilelocation.startsWith(genRoot + path.sep)) {
rel = path.relative(genRoot, importedFilelocation);
} else if (importedFilelocation === root || importedFilelocation.startsWith(root + path.sep)) {
rel = path.relative(root, importedFilelocation);
}
}
let normalizedRel = rel.replaceAll('\\', '/');
const frontEndIndex = normalizedRel.indexOf('front_end/');
if (frontEndIndex !== -1) {
normalizedRel = normalizedRel.slice(frontEndIndex);
}
const isExternal = bundleAll ? false : Boolean(externalFiles && externalFiles.has(normalizedRel));
return {
id: importedFilelocation,
external: isExternal,
};
}
export function esbuildPlugin(outdir, genRoot, rootDir, externalFiles, bundleAll = false) {
if (!bundleAll && (!externalFiles || !(externalFiles instanceof Set))) {
throw new Error('esbuildPlugin requires an externalFiles Set');
}
if (!outdir || typeof outdir !== 'string') {
throw new Error('esbuildPlugin requires an outdir path');
}
if (!genRoot || typeof genRoot !== 'string') {
throw new Error('esbuildPlugin requires a genRoot path');
}
if (!rootDir || typeof rootDir !== 'string') {
throw new Error('esbuildPlugin requires a rootDir path');
}
const normGenRoot = path.resolve(genRoot);
const root = path.resolve(rootDir);
const normOutdir = path.resolve(outdir);
return args => {
// args.importer is absolute path in esbuild.
const res = devtoolsPlugin(args.path, args.importer, externalFiles, root, normGenRoot, bundleAll);
if (!res) {
return null;
}
const isUnderGenRoot =
path.isAbsolute(res.id) && (res.id === normGenRoot || res.id.startsWith(normGenRoot + path.sep));
if (res.external) {
// res.id can be both of absolutized local JavaScript path or node's
// builtin module (e.g. 'fs', 'path'), and only relativize the path in
// former case.
if (path.isAbsolute(res.id)) {
const targetInGen = isUnderGenRoot ? res.id : path.join(normGenRoot, path.relative(root, res.id));
let rel = path.relative(normOutdir, targetInGen);
if (!/^\.\.?($|\/|\\)/.test(rel)) {
rel = './' + rel;
}
res.id = rel.replaceAll('\\', '/');
}
return {
external: res.external,
path: res.id,
};
}
// For non-external imports to inline:
// 1. If exact JS file exists in source tree:
if (fs.existsSync(res.id)) {
return {
path: res.id,
};
}
// 2. If TS file exists in source tree:
if (res.id.endsWith('.js')) {
const tsLocation = res.id.slice(0, -3) + '.ts';
if (fs.existsSync(tsLocation)) {
return {
path: tsLocation,
};
}
}
// 3. If file exists in gen directory (e.g. .css.js or generated JS):
if (!isUnderGenRoot) {
const relFromRoot = path.relative(root, res.id);
const genLocation = path.join(normGenRoot, relFromRoot);
if (fs.existsSync(genLocation)) {
return {
path: genLocation,
};
}
}
return {
path: res.id,
};
};
}