Skip to content

Commit

Permalink
Expose unstable_transform and unstable_resolve APIs (#9193)
Browse files Browse the repository at this point in the history
  • Loading branch information
kiddkai authored Sep 8, 2023
1 parent 0b5edcf commit cd0edbc
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 13 deletions.
95 changes: 94 additions & 1 deletion packages/core/core/src/Parcel.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// @flow strict-local

import type {
Asset,
AsyncSubscription,
BuildEvent,
BuildSuccessEvent,
InitialParcelOptions,
PackagedBundle as IPackagedBundle,
ParcelTransformOptions,
ParcelResolveOptions,
ParcelResolveResult,
} from '@parcel/types';
import path from 'path';
import type {ParcelOptions} from './types';
// eslint-disable-next-line no-unused-vars
import type {FarmOptions, SharedReference} from '@parcel/workers';
Expand Down Expand Up @@ -37,10 +42,18 @@ import RequestTracker, {
} from './RequestTracker';
import createValidationRequest from './requests/ValidationRequest';
import createParcelBuildRequest from './requests/ParcelBuildRequest';
import createAssetRequest from './requests/AssetRequest';
import createPathRequest from './requests/PathRequest';
import {createEnvironment} from './Environment';
import {createDependency} from './Dependency';
import {Disposable} from '@parcel/events';
import {init as initSourcemaps} from '@parcel/source-map';
import {init as initHash} from '@parcel/hash';
import {toProjectPath} from './projectPath';
import {
fromProjectPath,
toProjectPath,
fromProjectPathRelative,
} from './projectPath';
import {tracer} from '@parcel/profiler';

registerCoreWithSerializer();
Expand Down Expand Up @@ -437,6 +450,86 @@ export default class Parcel {
logger.info({origin: '@parcel/core', message: 'Taking heap snapshot...'});
return this.#farm.takeHeapSnapshot();
}

async unstable_transform(
options: ParcelTransformOptions,
): Promise<Array<Asset>> {
if (!this.#initialized) {
await this._init();
}

let projectRoot = nullthrows(this.#resolvedOptions).projectRoot;
let request = createAssetRequest({
...options,
filePath: toProjectPath(projectRoot, options.filePath),
optionsRef: this.#optionsRef,
env: createEnvironment({
...options.env,
loc:
options.env?.loc != null
? {
...options.env.loc,
filePath: toProjectPath(projectRoot, options.env.loc.filePath),
}
: undefined,
}),
});

let res = await this.#requestTracker.runRequest(request, {
force: true,
});
return res.map(asset =>
assetFromValue(asset, nullthrows(this.#resolvedOptions)),
);
}

async unstable_resolve(
request: ParcelResolveOptions,
): Promise<?ParcelResolveResult> {
if (!this.#initialized) {
await this._init();
}

let projectRoot = nullthrows(this.#resolvedOptions).projectRoot;
if (request.resolveFrom == null && path.isAbsolute(request.specifier)) {
request.specifier = fromProjectPathRelative(
toProjectPath(projectRoot, request.specifier),
);
}

let dependency = createDependency(projectRoot, {
...request,
env: createEnvironment({
...request.env,
loc:
request.env?.loc != null
? {
...request.env.loc,
filePath: toProjectPath(projectRoot, request.env.loc.filePath),
}
: undefined,
}),
});

let req = createPathRequest({
dependency,
name: request.specifier,
});

let res = await this.#requestTracker.runRequest(req, {
force: true,
});
if (!res) {
return null;
}

return {
filePath: fromProjectPath(projectRoot, res.filePath),
code: res.code,
query: res.query,
sideEffects: res.sideEffects,
};
}
}

export class BuildError extends ThrowableDiagnostic {
Expand Down
53 changes: 53 additions & 0 deletions packages/core/core/test/Parcel.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,59 @@ describe('Parcel', function () {
});
});

describe('ParcelAPI', function () {
this.timeout(75000);

let workerFarm;
beforeEach(() => {
workerFarm = createWorkerFarm();
});

afterEach(() => workerFarm.end());

describe('parcel.unstable_transform()', () => {
it('should transforms simple file', async () => {
let parcel = createParcel({workerFarm});
let res = await parcel.unstable_transform({
filePath: path.join(__dirname, 'fixtures/parcel/index.js'),
});
let code = await res[0].getCode();
assert(code.includes('exports.default = "test"'));
});

it('should transform with standalone mode', async () => {
let parcel = createParcel({workerFarm});
let res = await parcel.unstable_transform({
filePath: path.join(__dirname, 'fixtures/parcel/other.js'),
query: 'standalone=true',
});
let code = await res[0].getCode();

assert(code.includes('require("./index.js")'));
assert(code.includes('new URL("index.js", "file:" + __filename);'));
assert(code.includes('import("index.js")'));
});
});

describe('parcel.resolve()', () => {
it('should resolve dependencies', async () => {
let parcel = createParcel({workerFarm});
let res = await parcel.unstable_resolve({
specifier: './other',
specifierType: 'esm',
resolveFrom: path.join(__dirname, 'fixtures/parcel/index.js'),
});

assert.deepEqual(res, {
filePath: path.join(__dirname, 'fixtures/parcel/other.js'),
code: undefined,
query: undefined,
sideEffects: true,
});
});
});
});

function createParcel(opts?: InitialParcelOptions) {
return new Parcel({
entries: [path.join(__dirname, 'fixtures/parcel/index.js')],
Expand Down
1 change: 1 addition & 0 deletions packages/core/core/test/fixtures/parcel/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'test';
3 changes: 3 additions & 0 deletions packages/core/core/test/fixtures/parcel/other.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as idx from './index.js';
new URL('index.js', import.meta.url);
import('index.js')
21 changes: 21 additions & 0 deletions packages/core/types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,27 @@ export type ASTGenerator = {|

export type BundleBehavior = 'inline' | 'isolated';

export type ParcelTransformOptions = {|
filePath: FilePath,
code?: string,
env?: EnvironmentOptions,
query?: ?string,
|};

export type ParcelResolveOptions = {|
specifier: DependencySpecifier,
specifierType: SpecifierType,
env?: EnvironmentOptions,
resolveFrom?: FilePath,
|};

export type ParcelResolveResult = {|
filePath: FilePath,
code?: string,
query?: ?string,
sideEffects?: boolean,
|};

/**
* An asset represents a file or part of a file. It may represent any data type, including source code,
* binary data, etc. Assets may exist in the file system or may be virtual.
Expand Down
29 changes: 17 additions & 12 deletions packages/transformers/js/core/src/dependency_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,16 @@ impl<'a> DependencyCollector<'a> {
None
}
}
_ => Some(format!(
_ if !self.config.standalone => Some(format!(
"{:x}",
hash!(format!(
"{}:{}:{}",
self.get_project_relative_filename(),
specifier,
kind
))
)),
)),
_ => None,
};

self.items.push(DependencyDescriptor {
Expand All @@ -158,7 +159,7 @@ impl<'a> DependencyCollector<'a> {
source_type: SourceType,
) -> ast::Expr {
// If not a library, replace with a require call pointing to a runtime that will resolve the url dynamically.
if !self.config.is_library {
if !self.config.is_library && !self.config.standalone {
let placeholder =
self.add_dependency(specifier.clone(), span, kind, None, false, source_type);
let specifier = if let Some(placeholder) = placeholder {
Expand All @@ -172,13 +173,17 @@ impl<'a> DependencyCollector<'a> {
// For library builds, we need to create something that can be statically analyzed by another bundler,
// so rather than replacing with a require call that is resolved by a runtime, replace with a `new URL`
// call with a placeholder for the relative path to be replaced during packaging.
let placeholder = format!(
"{:x}",
hash!(format!(
"parcel_url:{}:{}:{}",
self.config.filename, specifier, kind
))
);
let placeholder = if self.config.standalone {
specifier.as_ref().into()
} else {
format!(
"{:x}",
hash!(format!(
"parcel_url:{}:{}:{}",
self.config.filename, specifier, kind
))
)
};
self.items.push(DependencyDescriptor {
kind,
loc: SourceLocation::from(self.source_map, span),
Expand Down Expand Up @@ -666,7 +671,7 @@ impl<'a> Fold for DependencyCollector<'a> {
// Replace import() with require()
if kind == DependencyKind::DynamicImport {
let mut call = node;
if !self.config.scope_hoist {
if !self.config.scope_hoist && !self.config.standalone {
let name = match &self.config.source_type {
SourceType::Module => "require",
SourceType::Script => "__parcel__require__",
Expand Down Expand Up @@ -838,7 +843,7 @@ impl<'a> Fold for DependencyCollector<'a> {

// If this is a library, we will already have a URL object. Otherwise, we need to
// construct one from the string returned by the JSRuntime.
if !self.config.is_library {
if !self.config.is_library && !self.config.standalone {
return Expr::New(NewExpr {
span: DUMMY_SP,
callee: Box::new(Expr::Ident(Ident::new(js_word!("URL"), DUMMY_SP))),
Expand Down
1 change: 1 addition & 0 deletions packages/transformers/js/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ pub struct Config {
is_esm_output: bool,
trace_bailouts: bool,
is_swc_helpers: bool,
standalone: bool,
}

#[derive(Serialize, Debug, Default)]
Expand Down
1 change: 1 addition & 0 deletions packages/transformers/js/src/JSTransformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ export default (new Transformer({
is_esm_output: asset.env.outputFormat === 'esmodule',
trace_bailouts: options.logLevel === 'verbose',
is_swc_helpers: /@swc[/\\]helpers/.test(asset.filePath),
standalone: asset.query.has('standalone'),
});

let convertLoc = (loc): SourceLocation => {
Expand Down

0 comments on commit cd0edbc

Please sign in to comment.