Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement absoluteImports option #40

Merged
merged 3 commits into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ The `shouldInjectPolyfill` function takes two parameters: the name of the polyfi
function shouldInjectPolyfill(name: string, defaultShouldInject: boolean): boolean;
```

### `absoluteRuntime`

`boolean` or `string`, defaults to `false`.

This allows users to run polyfill providers broadly across a whole project. By default, polyfill providers inject a bare import to the polyfill package (for example, `core-js-pure/stable/array/from.js`), but that only works if the polyfill is in the `node_modules` of the file that is being compiled. This can be problematic for nested `node_modules`, npm-linked modules, or CLIs that reside outside the user's project, among other cases. To avoid worrying about how the runtime module's location is resolved, this allows users to resolve the runtime once up front, and then insert absolute paths to the runtime into the output code.

Using absolute paths is not desirable if files are compiled for use at a later time, but in contexts where a file is compiled and then immediately consumed, they can be quite helpful.

Reference: [babel/babel#8435](https://github.com/babel/babel/pull/8435)

### `missingDependencies`

This option modifies the dependencies detection logging. If set to `false`, dependencies
Expand Down
52 changes: 50 additions & 2 deletions packages/babel-helper-define-polyfill-provider/src/dependencies.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,59 @@
// @flow

import resolve from "resolve";
import requireResolve from "resolve";
import path from "path";
import debounce from "lodash.debounce";

export function resolve(
dirname: string,
moduleName: string,
absoluteImports: boolean | string,
): string {
if (absoluteImports === false) return moduleName;

let basedir = dirname;
if (typeof absoluteImports === "string") {
basedir = path.resolve(basedir, absoluteImports);
}

let modulePackage, moduleNestedPath;

let slash = moduleName.indexOf("/");
if (moduleName[0] === "@") {
slash = moduleName.indexOf("/", slash + 1);
}

if (slash === -1) {
modulePackage = moduleName;
moduleNestedPath = "";
} else {
modulePackage = moduleName.slice(0, slash);
moduleNestedPath = moduleName.slice(slash);
}

try {
const pkg = requireResolve.sync(`${modulePackage}/package.json`, {
basedir,
});
return path.dirname(pkg) + moduleNestedPath;
} catch (err) {
if (err.code !== "MODULE_NOT_FOUND") throw err;

// $FlowIgnore
throw Object.assign(
new Error(`Failed to resolve "${moduleName}" relative to "${dirname}"`),
{
code: "BABEL_POLYFILL_NOT_FOUND",
polyfill: moduleName,
dirname,
},
);
}
}

export function has(basedir: string, name: string) {
try {
resolve.sync(name, { basedir });
requireResolve.sync(name, { basedir });
return true;
} catch {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ export default class ImportsCache {
_imports: WeakMap<NodePath, StrMap<string>>;
_anonymousImports: WeakMap<NodePath, Set<string>>;
_lastImports: WeakMap<NodePath, NodePath>;
_resolver: (url: string) => string;

constructor() {
constructor(resolver: (url: string) => string) {
this._imports = new WeakMap();
this._anonymousImports = new WeakMap();
this._lastImports = new WeakMap();
this._resolver = resolver;
}

storeAnonymous(
Expand All @@ -29,7 +31,7 @@ export default class ImportsCache {

const node = getVal(
programPath.node.sourceType === "script",
t.stringLiteral(url),
t.stringLiteral(this._resolver(url)),
);
imports.add(key);
this._injectImport(programPath, node);
Expand All @@ -53,7 +55,7 @@ export default class ImportsCache {
if (!imports.has(key)) {
const { node, name: id } = getVal(
programPath.node.sourceType === "script",
t.stringLiteral(url),
t.stringLiteral(this._resolver(url)),
t.identifier(name),
);
imports.set(key, id);
Expand Down
29 changes: 27 additions & 2 deletions packages/babel-helper-define-polyfill-provider/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ function resolveOptions<Options>(
debug: boolean,
shouldInjectPolyfill: ?(name: string, shouldInject: boolean) => boolean,
providerOptions: ProviderOptions<Options>,
absoluteImports: string | boolean,
} {
const {
method,
Expand All @@ -53,6 +54,7 @@ function resolveOptions<Options>(
configPath,
debug,
shouldInjectPolyfill,
absoluteImports,
...providerOptions
} = options;

Expand Down Expand Up @@ -83,6 +85,17 @@ function resolveOptions<Options>(
);
}

if (
absoluteImports != null &&
typeof absoluteImports !== "boolean" &&
typeof absoluteImports !== "string"
) {
throw new Error(
`.absoluteImports must be a boolean, a string, or undefined` +
` (received ${JSON.stringify(absoluteImports)})`,
);
}

const targetsObj =
typeof targetsOption === "string" || Array.isArray(targetsOption)
? { browsers: targetsOption }
Expand All @@ -97,6 +110,7 @@ function resolveOptions<Options>(
method,
methodName,
targets,
absoluteImports: absoluteImports ?? false,
shouldInjectPolyfill,
debug: !!debug,
providerOptions: ((providerOptions: Object): ProviderOptions<Options>),
Expand All @@ -118,9 +132,14 @@ function instantiateProvider<Options>(
debug,
shouldInjectPolyfill,
providerOptions,
absoluteImports,
} = resolveOptions<Options>(options);

const getUtils = createUtilsGetter(new ImportsCache());
const getUtils = createUtilsGetter(
new ImportsCache(moduleName =>
deps.resolve(dirname, moduleName, absoluteImports),
),
);

// eslint-disable-next-line prefer-const
let include, exclude;
Expand Down Expand Up @@ -180,12 +199,18 @@ function instantiateProvider<Options>(
},
assertDependency(name, version = "*") {
if (missingDependencies === false) return;
if (absoluteImports) {
// If absoluteImports is not false, we will try resolving
// the dependency and throw if it's not possible. We can
// skip the check here.
return;
}

const dep = version === "*" ? name : `${name}@^${version}`;

const found = missingDependencies.all
? false
: mapGetOr(depsCache, name, () => deps.has(dirname, name));
: mapGetOr(depsCache, name, () => !deps.has(dirname, name));

if (!found) {
debugLog().missingDeps.add(dep);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type PluginOptions = {|
exclude?: Pattern[],
shouldInjectPolyfill?: (name: string, shouldInject: boolean) => boolean,
missingDependencies?: MissingDependenciesOption,
absoluteImports?: boolean | string,
|};

export type PolyfillProvider<Opts = {||}> = (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as babel from "@babel/core";
import { join } from "path";
import { readFileSync, writeFileSync } from "fs";

function transformTest(name, cwd, file, options = {}) {
it(name, () => {
const inputPath = join(cwd, file);
const outputPath = join(cwd, file.replace(".js", ".out.js"));

const input = readFileSync(inputPath, "utf8");
let expected;
try {
expected = readFileSync(outputPath, "utf8");
} catch {}

let { code } = babel.transformSync(input, {
cwd,
filename: inputPath,
...options,
});

while (code.includes(__dirname)) {
code = code.replace(__dirname, "<CWD>");
}

if (expected === undefined) {
writeFileSync(outputPath, code);
} else {
expect(code).toBe(expected);
}
});
}

describe("true", () => {
const cwd = join(__dirname, "fixtures", "absoluteImports", "true");

transformTest("basic behavior", cwd, "main.js");
transformTest("relative to config file", cwd, "nested/main.js");
transformTest("resolved in a parent directory", cwd, "nested-2/main.js");
});

describe("string", () => {
const cwd = join(__dirname, "fixtures", "absoluteImports", "string");

transformTest("basic behavior", cwd, "main.js");
});

describe("subpath", () => {
const cwd = join(__dirname, "fixtures", "absoluteImports", "subpath");

transformTest("works", cwd, "main.js");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"plugins": [
["../../../helpers/generic-provider.js", {
"method": "usage-global",
"globals": {
"a": "polyfill-a",
"b": "polyfill-b"
},
"absoluteImports": "./nested"
}, "polyfill-a"]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
a;
b;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import "<CWD>/fixtures/absoluteImports/string/nested/node_modules/polyfill-a";
import "<CWD>/fixtures/absoluteImports/string/node_modules/polyfill-b";
a;
b;

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"plugins": [
["../../../helpers/generic-provider.js", {
"method": "usage-global",
"globals": {
"a": "polyfill-a/auto",
"b": "@polyfill/b/auto"
},
"absoluteImports": true
}]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
a;
b;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import "<CWD>/fixtures/absoluteImports/subpath/node_modules/polyfill-a/auto";
import "<CWD>/fixtures/absoluteImports/subpath/node_modules/@polyfill/b/auto";
a;
b;

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"babelrcRoots": ["./nested", "./nested-2"],
"plugins": [
["../../../helpers/generic-provider.js", {
"method": "usage-global",
"globals": {
"a": "polyfill-a"
},
"absoluteImports": true
}, "polyfill-a"]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import "<CWD>/fixtures/absoluteImports/true/node_modules/polyfill-a";
a;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"plugins": [
["../../../../helpers/generic-provider.js", {
"method": "usage-global",
"globals": {
"b": "polyfill-b"
},
"absoluteImports": true
}, "polyfill-b"]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
a;
b;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import "<CWD>/fixtures/absoluteImports/true/node_modules/polyfill-b";
import "<CWD>/fixtures/absoluteImports/true/node_modules/polyfill-a";
a;
b;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"plugins": [
["../../../../helpers/generic-provider.js", {
"method": "usage-global",
"globals": {
"b": "polyfill-b"
},
"absoluteImports": true
}, "polyfill-b"]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
a;
b;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import "<CWD>/fixtures/absoluteImports/true/nested/node_modules/polyfill-b";
import "<CWD>/fixtures/absoluteImports/true/node_modules/polyfill-a";
a;
b;

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading