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 support for classic scripts and automatic module/nomodule #6247

Merged
merged 15 commits into from
Jun 19, 2021

Conversation

devongovett
Copy link
Member

@devongovett devongovett commented May 9, 2021

This implements #5398 on top of the new SWC-based JS transformer. It takes a slightly different approach: rather than adding support for multiple contexts to the environment, it adds a sourceType field, which can either be script for classic scripts, or module for ES modules.

The other difference from the original implementation is the way scripts hoist their top-level variables into the global scope. In the previous implementation, this was done by attempting to find all top-level vars and assign them to the global object. In this implementation, the code for the entry asset of script bundles itself is moved outside the bundle wrapper. Any require calls that the entry asset might do (e.g. for runtimes) are replaced with a parcelRequire call.

Otherwise, everything is the same as the previous PR. See there for details.

The posthtml parser fork for adding location information will be removed once posthtml/posthtml-parser#63 is merged and released. DONE

Close T-136. Fixes #333. Closes #3011.

@height
Copy link

height bot commented May 9, 2021

This pull request has been linked to and will mark 2 tasks as "Done" when merged:

});

let convertLoc = loc => ({
filePath: relativePath,
start: {
line: loc.start_line,
line: loc.start_line + Number(asset.meta.startLine ?? 1) - 1,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of a hack. Should we do something in core for this?

decls: &decls,
used_env: &mut result.used_env
},
config.source_type != SourceType::Script
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should process.env be supported in scripts?

Copy link
Member

@mischnic mischnic Jun 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it would be nice? But we could also add that later if it comes up. Was there a reason you disabled it?
The only thing we can't support if course is the process Node global replacement.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly I was just trying to make scripts as close to real browser JS as possible, and not enable any CommonJS/Node features.

Base automatically changed from swc-scope-hoisting to v2 May 10, 2021 00:13
# Conflicts:
#	packages/core/core/src/Environment.js
#	packages/core/core/test/Environment.test.js
#	packages/core/core/test/TargetRequest.test.js
#	packages/transformers/html/src/dependencies.js
#	packages/transformers/js/core/src/global_replacer.rs
#	packages/transformers/js/src/JSTransformer.js
Comment on lines +178 to +188
delete attrs.type;
attrs.nomodule = '';
attrs.src = asset.addURLDependency(attrs.src, {
// Keep in the same bundle group as the HTML.
priority: 'parallel',
env: {
sourceType,
outputFormat: 'global',
loc,
},
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these statements do anything? attrs is a shallow clone and it was already merged into copy

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attrs is referenced by copy, so this is modifying the attrs of the copy.

packages/core/core/src/Environment.js Outdated Show resolved Hide resolved
packages/core/core/src/Environment.js Outdated Show resolved Hide resolved
packages/core/core/src/public/Environment.js Outdated Show resolved Hide resolved
packages/core/types/index.js Show resolved Hide resolved
@@ -6,7 +6,7 @@ import {createEnvironment} from '../src/Environment';
describe('Environment', () => {
it('assigns a default environment with nothing passed', () => {
assert.deepEqual(createEnvironment(), {
id: '1051b306b4fe6e64',
id: '2163092a3dd83a5f',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😞

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be neat if we used/made something along the lines of Jest's stringMatching matcher: https://jestjs.io/docs/expect#expectstringmatchingstring--regexp

@@ -428,7 +428,7 @@ describe('sourcemaps', function() {
source: inputs[0],
generated: raw,
str: 'const local',
generatedStr: 'const o',
generatedStr: 'const t',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😞

@@ -70,6 +70,7 @@ impl<'a> Fold for GlobalReplacer<'a> {
attributes: None,
is_optional: false,
is_helper: false,
source_type: Some(SourceType::Module),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be None as in fs.rs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file dependencies don't need a source type, but in this case it's referring to a require that should be treated as a module.

Copy link
Contributor

@wbinnssmith wbinnssmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@@ -148,7 +148,7 @@ export class CJSOutputFormat implements OutputFormat {
return [res, lines];
}

buildBundlePostlude(): string {
return '';
buildBundlePostlude(): [string, number] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Maybe a type alias or a comment explaining what these values in the return type mean?

@devongovett devongovett merged commit dfb179b into v2 Jun 19, 2021
@devongovett devongovett deleted the script-module branch June 19, 2021 00:52
@rhendric
Copy link
Contributor

Found my way here from the new "Browser scripts cannot have imports or exports" error. Why does this restriction exist, and can it be suppressed if we know that Parcel is going to bundle away all of the import statements?

@devongovett
Copy link
Member Author

devongovett commented Jun 25, 2021

You just need to add type="module" to your script tag and then it should work fine. If your browser targets defined in your browserslist don't all support modules natively, Parcel will auto generate a nomodule version as well.

@rhendric
Copy link
Contributor

What if I only want to produce one bundle for all browsers? If everything's being bundled together, what does it matter if the bundle is being loaded as a module or not? Seems unusually opinionated for Parcel to force there to be two essentially identical versions of the bundle; what are we getting for this?

@devongovett
Copy link
Member Author

The main reason is to ship less code to most browsers. 92.64% of global web traffic is via a browser that supports ES modules natively. This also means that we can ship a lot more modern JS features like classes etc. without transpilation which results in reduced bundle sizes. Basically, unless you have to support IE 11 still, you will get one bundle anyway, and if you do, then most of your users will need to download less code, and only IE 11 users will need to download the larger legacy bundle.

@rhendric
Copy link
Contributor

Hmm, okay. I still don't see why Parcel couldn't just support script tags without the module type by doing exactly what it does to generate a nomodule bundle, or what transpilation of other JS features has to do with modules and imports/exports (which are completely eliminated in bundles regardless of what features the browser supports), but I can try targeting modules and seeing if that causes any problems for me.

@devongovett
Copy link
Member Author

Yeah it's because scripts behave differently in other ways as well. Whereas modules are self contained, scripts run in the global context meaning any variables created become global. This might be seen as a bad idea but there are lots of legacy libraries like jquery that rely on this assumption. The goal of Parcel is to work exactly like if you ran your code natively in a browser but also make your code portable to older browsers as well if needed. Treating scripts as modules in Parcel v1 was a mistake that we're trying to correct in v2.

@rhendric
Copy link
Contributor

Ah, so this is a way to support

<script src="jquery.js"></script>
<script type="module" src="app.js"></script>

such that on old browsers, jquery.js is loaded as-is but app.js is wrapped in an IIFE or something to keep it from adding to the global scope?

@devongovett
Copy link
Member Author

Yeah exactly. It solves two separate problems that we've had for a long time - legacy script support, and automatic modern/legacy builds for reduced bundle sizes. It'll be a transition for everyone to get used to for sure. If you think there's an improvement we can make to the error message, we'd love to hear suggestions. Maybe we can add a link to the docs with some more info or something.

@ivoba
Copy link

ivoba commented Jun 30, 2021

This is awesome!
@devongovett is this already in 2.0.0-nightly.746 ?

I have classic imports but they still seem to be wrapped as modules.

    <script src="/node_modules/jquery/dist/jquery.min.js"></script>
    <script src="/js/jquery.cycle.lite.js"></script>

which results in:

$(...).cycle is not a function

@mischnic
Copy link
Member

is this already in 2.0.0-nightly.746 ?

Yes

$(...).cycle is not a function

Can you open an issue for that with a full code sample (not sure if there's more needed that you already posted)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Parcel 2: Differential Bundling Some dependencies that rely on window don't work
5 participants