Skip to content

Commit

Permalink
feat(@parcel/transformer-js): Rewrite __dirname/__filename to be …
Browse files Browse the repository at this point in the history
…relative to `asset.filePath` (#7727)

Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
  • Loading branch information
LekoArts and mischnic committed Apr 11, 2022
1 parent 6be49aa commit be0929a
Show file tree
Hide file tree
Showing 13 changed files with 466 additions and 3 deletions.
5 changes: 4 additions & 1 deletion packages/core/integration-tests/test/fs.js
Expand Up @@ -201,7 +201,10 @@ describe('fs', function () {
assert(contents.includes("require('fs')"));
assert(contents.includes('readFileSync'));

await outputFS.writeFile(path.join(distDir, 'test.txt'), 'hey');
await outputFS.writeFile(
path.join(__dirname, '/integration/fs-node/', 'test.txt'),
'hey',
);
let output = await run(b);
assert.equal(output, 'hey');
});
Expand Down
@@ -0,0 +1,24 @@
const fs = require('fs');
const path = require('path');
const otherFunction = require('./other/function')
const subFunction = require('./sub/index')

module.exports = function () {
const data = fs.readFileSync(path.join(__dirname, 'data', 'test.txt'), 'utf8')
const firstDirnameTest = path.join(__dirname, 'data')
const secondDirnameTest = path.join(__dirname, 'other-data')
const firstFilenameTest = __filename
const secondFilenameTest = `${__filename}?query-string=test`
const other = otherFunction()
const sub = subFunction()

return {
data,
firstDirnameTest,
secondDirnameTest,
firstFilenameTest,
secondFilenameTest,
other,
sub,
}
}
@@ -0,0 +1,6 @@
const fs = require('fs');
const path = require('path');

module.exports = function () {
return fs.readFileSync(path.join(__dirname, '..', 'data', 'test.txt'), 'utf8')
}
@@ -0,0 +1,6 @@
{
"name": "env-node-replacements",
"engines": {
"node": ">=14"
}
}
@@ -0,0 +1,6 @@
module.exports = function () {
return {
dirname: __dirname,
filename: __filename
}
}
Empty file.
135 changes: 135 additions & 0 deletions packages/core/integration-tests/test/javascript.js
Expand Up @@ -2678,6 +2678,141 @@ describe('javascript', function () {
});
});

it('should replace __dirname and __filename with path relative to asset.filePath', async function () {
let b = await bundle(
path.join(__dirname, '/integration/env-node-replacements/index.js'),
);

let dist = await outputFS.readFile(b.getBundles()[0].filePath, 'utf8');
assert(
dist.includes(
'require("path").resolve(__dirname, "../test/integration/env-node-replacements")',
),
);
assert(
dist.includes(
'require("path").resolve(__dirname, "../test/integration/env-node-replacements/other")',
),
);
assert(
dist.includes(
'require("path").resolve(__dirname, "../test/integration/env-node-replacements", "index.js")',
),
);
assert(
dist.includes(
'require("path").resolve(__dirname, "../test/integration/env-node-replacements/sub")',
),
);
assert(
dist.includes(
'require("path").resolve(__dirname, "../test/integration/env-node-replacements/sub", "index.js")',
),
);
let f = await run(b);
let output = f();
assert.equal(output.data, 'hello');
assert.equal(output.other, 'hello');
assert.equal(
output.firstDirnameTest,
path.join(__dirname, '/integration/env-node-replacements/data'),
);
assert.equal(
output.secondDirnameTest,
path.join(__dirname, '/integration/env-node-replacements/other-data'),
);
assert.equal(
output.firstFilenameTest,
path.join(__dirname, '/integration/env-node-replacements/index.js'),
);
assert.equal(
output.secondFilenameTest,
path.join(
__dirname,
'/integration/env-node-replacements/index.js?query-string=test',
),
);
assert.equal(
output.sub.dirname,
path.join(__dirname, '/integration/env-node-replacements/sub'),
);
assert.equal(
output.sub.filename,
path.join(__dirname, '/integration/env-node-replacements/sub/index.js'),
);
});

it('should replace __dirname and __filename with path relative to asset.filePath with scope hoisting', async function () {
let b = await bundle(
path.join(__dirname, '/integration/env-node-replacements/index.js'),
{
mode: 'production',
defaultTargetOptions: {
shouldScopeHoist: true,
shouldOptimize: false,
},
},
);

let dist = await outputFS.readFile(b.getBundles()[0].filePath, 'utf8');
assert(
dist.includes(
'path.resolve(__dirname, "../test/integration/env-node-replacements")',
),
);
assert(
dist.includes(
'path.resolve(__dirname, "../test/integration/env-node-replacements/other")',
),
);
assert(
dist.includes(
'path.resolve(__dirname, "../test/integration/env-node-replacements", "index.js")',
),
);
assert(
dist.includes(
'path.resolve(__dirname, "../test/integration/env-node-replacements/sub")',
),
);
assert(
dist.includes(
'path.resolve(__dirname, "../test/integration/env-node-replacements/sub", "index.js")',
),
);
let f = await run(b);
let output = f();
assert.equal(output.data, 'hello');
assert.equal(output.other, 'hello');
assert.equal(
output.firstDirnameTest,
path.join(__dirname, '/integration/env-node-replacements/data'),
);
assert.equal(
output.secondDirnameTest,
path.join(__dirname, '/integration/env-node-replacements/other-data'),
);
assert.equal(
output.firstFilenameTest,
path.join(__dirname, '/integration/env-node-replacements/index.js'),
);
assert.equal(
output.secondFilenameTest,
path.join(
__dirname,
'/integration/env-node-replacements/index.js?query-string=test',
),
);
assert.equal(
output.sub.dirname,
path.join(__dirname, '/integration/env-node-replacements/sub'),
);
assert.equal(
output.sub.filename,
path.join(__dirname, '/integration/env-node-replacements/sub/index.js'),
);
});

it('should work when multiple files use globals with scope hoisting', async function () {
let b = await bundle(
path.join(__dirname, '/integration/globals/multiple.js'),
Expand Down
21 changes: 20 additions & 1 deletion packages/packagers/js/src/DevPackager.js
@@ -1,7 +1,12 @@
// @flow strict-local
import type {BundleGraph, PluginOptions, NamedBundle} from '@parcel/types';

import {PromiseQueue, relativeBundlePath, countLines} from '@parcel/utils';
import {
PromiseQueue,
relativeBundlePath,
countLines,
normalizeSeparators,
} from '@parcel/utils';
import SourceMap from '@parcel/source-map';
import invariant from 'assert';
import path from 'path';
Expand Down Expand Up @@ -116,6 +121,20 @@ export class DevPackager {
wrapped += JSON.stringify(deps);
wrapped += ']';

if (
this.bundle.env.isNode() &&
asset.meta.has_node_replacements === true
) {
const relPath = normalizeSeparators(
path.relative(
this.bundle.target.distDir,
path.dirname(asset.filePath),
),
);
wrapped = wrapped.replace('$parcel$dirnameReplace', relPath);
wrapped = wrapped.replace('$parcel$filenameReplace', relPath);
}

if (this.bundle.env.sourceMap) {
if (mapBuffer) {
map.addBuffer(mapBuffer, lineOffset);
Expand Down
16 changes: 15 additions & 1 deletion packages/packagers/js/src/ScopeHoistingPackager.js
Expand Up @@ -8,12 +8,18 @@ import type {
NamedBundle,
} from '@parcel/types';

import {PromiseQueue, relativeBundlePath, countLines} from '@parcel/utils';
import {
PromiseQueue,
relativeBundlePath,
countLines,
normalizeSeparators,
} from '@parcel/utils';
import SourceMap from '@parcel/source-map';
import nullthrows from 'nullthrows';
import invariant from 'assert';
import ThrowableDiagnostic from '@parcel/diagnostic';
import globals from 'globals';
import path from 'path';

import {ESMOutputFormat} from './ESMOutputFormat';
import {CJSOutputFormat} from './CJSOutputFormat';
Expand Down Expand Up @@ -405,6 +411,14 @@ export class ScopeHoistingPackager {
this.usedHelpers.add('$parcel$global');
}

if (this.bundle.env.isNode() && asset.meta.has_node_replacements) {
const relPath = normalizeSeparators(
path.relative(this.bundle.target.distDir, path.dirname(asset.filePath)),
);
code = code.replace('$parcel$dirnameReplace', relPath);
code = code.replace('$parcel$filenameReplace', relPath);
}

let [depMap, replacements] = this.buildReplacements(asset, deps);
let [prepend, prependLines, append] = this.buildAssetPrelude(asset, deps);
if (prependLines > 0) {
Expand Down
26 changes: 26 additions & 0 deletions packages/transformers/js/core/src/lib.rs
Expand Up @@ -18,6 +18,7 @@ mod fs;
mod global_replacer;
mod hoist;
mod modules;
mod node_replacer;
mod typeof_replacer;
mod utils;

Expand Down Expand Up @@ -51,6 +52,7 @@ use fs::inline_fs;
use global_replacer::GlobalReplacer;
use hoist::{hoist, CollectResult, HoistResult};
use modules::esm2cjs;
use node_replacer::NodeReplacer;
use typeof_replacer::*;
use utils::{CodeHighlight, Diagnostic, DiagnosticSeverity, SourceLocation, SourceType};

Expand All @@ -69,6 +71,7 @@ pub struct Config {
env: HashMap<swc_atoms::JsWord, swc_atoms::JsWord>,
inline_fs: bool,
insert_node_globals: bool,
node_replacer: bool,
is_browser: bool,
is_worker: bool,
is_type_script: bool,
Expand Down Expand Up @@ -102,6 +105,7 @@ pub struct TransformResult {
diagnostics: Option<Vec<Diagnostic>>,
needs_esm_helpers: bool,
used_env: HashSet<swc_atoms::JsWord>,
has_node_replacements: bool,
}

fn targets_to_versions(targets: &Option<HashMap<String, String>>) -> Option<Versions> {
Expand Down Expand Up @@ -396,6 +400,28 @@ pub fn transform(config: Config) -> Result<TransformResult, std::io::Error> {
module.fold_with(&mut passes)
};

let mut has_node_replacements = false;

let module = module.fold_with(
// Replace __dirname and __filename with placeholders in Node env
&mut Optional::new(
NodeReplacer {
source_map: &source_map,
items: &mut global_deps,
globals: HashMap::new(),
project_root: Path::new(&config.project_root),
filename: Path::new(&config.filename),
decls: &mut decls,
global_mark,
scope_hoist: config.scope_hoist,
has_node_replacements: &mut has_node_replacements,
},
config.node_replacer,
),
);

result.has_node_replacements = has_node_replacements;

let module = module.fold_with(
// Collect dependencies
&mut dependency_collector(
Expand Down

0 comments on commit be0929a

Please sign in to comment.