| // 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, |
| }; |
| }; |
| } |