GithubHelp home page GithubHelp logo

florianrappl / parcel-plugin-externals Goto Github PK

View Code? Open in Web Editor NEW
47.0 3.0 3.0 52 KB

Parcel plugin for declaring externals. These externals will not be bundled. :package:

Home Page: https://piral.io

License: MIT License

JavaScript 100.00%
parcel parcel-bundler parcel-plugin bundle externals dependencies

parcel-plugin-externals's Introduction

parcel-plugin-externals

Build Status npm GitHub tag GitHub issues

Parcel plugin for declaring externals. These externals will not be bundled. ๐Ÿ“ฆ

Usage

As with any other Parcel plugin you should make sure to have the Parcel bundler installed and the plugin referenced from the package.json of your project.

The package.json has to be changed to either contain peerDependencies or externals. The latter is more flexible.

Use Global Require

Consider the following snippet (from package.json):

{
  "peerDependencies": {
    "react": "*"
  }
}

This plugin will omit React from your bundle. Instead, a call to require('react') will be left over. If the global require inserted by Parcel does not know how to resolve it you will face an error.

Alternatively, you could have also written the following snippet (from package.json):

{
  "externals": [
    "react"
  ]
}

Use Global Variable

Potentially, instead you want to hint Parcel that you already have a global available coming from another script. The externals definition can help you.

Consider the following snippet (from package.json):

{
  "externals": {
    "react": "React"
  }
}

Here we tell the plugin to alias the react module with React. In this case we reference a global variable React, which obviously must exist.

Note: Don't confuse this with the abilities coming from parcel-plugin-html-externals. Values that are non-string instances will be ignored. So you can actually use both plugins, parcel-plugin-externals and parcel-plugin-html-externals if you want to (or just one of the two).

Use Custom Locations

The object syntax is a shorthand for combining the keys and values for a replacement expression. The snippet above is acutally equalivant to:

{
  "externals": [
    "react => React"
  ]
}

Expressions could be more complex:

{
  "externals": [
    "react => window.dependencies.React"
  ]
}

In this case dependencies must exist globally with React being one of its properties.

Alternatively, you could forward one module to another with require:

{
  "externals": [
    "react => require('preact')"
  ]
}

Important: This is an early version of the plugin. Please give feedback on GitHub, especially regarding configuration and syntax. The idea is to keep the plugin simple and the options straight and to the point.

Dynamic Dependency Resolution

Sometimes you want to externalize a whole set of dependencies, potentially by a given rule, e.g., react-* or similar. For such cases you can also refer to a module that does the replacement rule determination:

{
  "externals": "./tools/ruleFactory.js"
}

The rule factory module is just a simple Node.js module that exports a function:

const rx = /react-(.*?)\//;

module.exports = function(path) {
  const result = rx.exec(path);

  if (result) {
    const suffix = result[1];
    const name = suffix[0].toUpperCase() + suffix.substr(1);
    return `react-${suffix} => React${name}`;
  }

  return undefined;
};

What you need to return is either undefined (i.e., the module will not be externalized) or the replacement rule.

Remark: If the rule does not contain the forward => slash it will be interpreted as returnValue => require('returnValue'), where returnValue is the part returned from the function.

Virtual Modules

By default, the modules must be present in the local installation. Otherwise Parcel will complain. This is not always possible / the case.

In this scenario you'll need a virtual module. You can get support for this via Parcel's standard alias.

Consider the following snippet (from package.json):

{
  "externals": [
    "react",
    "foo"
  ],
  "alias": {
    "foo": "./src/foo-virtual.js"
  }
}

Here, we will identify that foo is an external alias and still externalize the call (in the given example to use require('foo')). You could leave the virtual module empty.

Important: If you have multiple virtual modules you should give them all unique paths. Otherwise, this plugin cannot distinguish between them.

Specialized Control

If you need further fine-grained control (e.g., for switching between development and production builds) you can just use the factory function introduced above.

To simplify you can also use the factory module to export the array directly.

const isProduction = process.env.NODE_ENV === 'production';
module.exports = isProduction ? [
  'my-dep',
  'react'
] : [
  'my-dep'
];

This way you can get flexibility without sacrificing convenience.

Changelog

This project adheres to semantic versioning.

You can find the changelog in the CHANGELOG.md file.

License

This plugin is released using the MIT license. For more information see the LICENSE file.

parcel-plugin-externals's People

Contributors

101arrowz avatar 4lejandrito avatar cliffordp avatar devil7dk avatar florianrappl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

parcel-plugin-externals's Issues

Uncaught ReferenceError: React is not defined

I'm able to exclude react from the build using "parcel-plugin-externals".

However, when I dynamically load the compiled script file into another project that already is using react, I get the error message "Uncaught ReferenceError: React is not defined". How do I make the script load the excluded module from the script it is being loaded into?

( If that make sense to you, I realize this is a very incomplete description )


More details on what I'm trying to do:

We are building a SPA application for a CMS using react, parcel and typescript.
We want to build a plugin system that will allow third party developers to create their own controls that plug into the CMS UI. These ui components must be loaded dynamically and from external sources.

We distribute our CMS components to NPMJS and third party developers will develop their plugins by referencing the NPM modules. The output of these projects should only contain the custom code they have written. I'm trying to use Parcel with "parcel-plugin-externals" to build a script file that can be loaded dynamically into the CMS at runtime.

To load the script file with:

const loadedScrips: Set<string> = new Set();
async function loadScript(url: string): Promise<void> {
    if (loadedScrips.has(url)) return;
    loadedScrips.add(url);
    return new Promise((resolve, reject) => {
        var script = document.createElement('script');
        script.onload = () => resolve();
        script.onerror = (err) => reject(err);
        script.src = url;
        document.head.appendChild(script);
    });
}

In the package,json we have added:

  "externals": {
    "react": "React"
  },

Without the "externals" it works, with the "externals" section it fails with:
"Uncaught ReferenceError: React is not defined"

If you want, I could make a simplified example and upload it to github.

Any help with the matter is greatly appreciated !

External syntax

Any reason the externals value has custom syntax with '=>' instead of an object? Would prefer something like this

{
  "externals": {
    "react": "React"
  }
}

over

{
  "externals": [
    "react => React"
  ]
}

Ignores hoisted modules

Hi!

I am happy user of your plugin. However I am trying to move a site using it to a package inside a monorepo that uses lerna + yarn workspaces.

With this setup all the dependencies are hoisted. This means the paths passded by parcel will be different from the ones calculated by this plugin and the external modules will not be externalized.

I've created a sample repo where you can see the issue in action: https://github.com/4lejandrito/parcel-plugin-externals-yarn-workspaces-issue-demo.

Run yarn && yarn start on it and you'll get a page that should have a dependency externalized (and therefore fail) but it will actually exist in the bundle and print a message on the screen.

HTML Externals

Just a quick thought, since I'm using the externals key in parcel-plugin-html-externals as well, that might introduce conflicts in case someone wanted to use both packages. Would you be interested in merging or introducing HTML support here?

Bundling fails when [email protected] is added to external

When adding [email protected] to externals bundling is failing due to the specified module doesn't exist.

ร— ENOENT: no such file or directory, lstat 'C:\Users<...>\node_modules\react-dom\unstable-fizz.browser.js'
at realpathSync (fs.js:1476:7)
at externals.push.modules.map (C:\Users<...>\node_modules\parcel-plugin-externals\utils.js:165:15)
at Array.map ()
at makeResolver (C:\Users<...>\node_modules\parcel-plugin-externals\utils.js:163:18)
at combineExternalsPrimitive (C:\Users<...>\node_modules\parcel-plugin-externals\utils.js:201:12)
at combineExternals (C:\Users<...>\node_modules\parcel-plugin-externals\utils.js:210:18)
at retrieveExternals (C:\Users<...>\node_modules\parcel-plugin-externals\utils.js:257:14)
at module.exports (C:\Users<...>\node_modules\parcel-plugin-externals\index.js:9:21)
at Bundler.loadPlugins (C:\Users<...>\node_modules\parcel\src\Bundler.js:219:17)

In package.json of react-dom:

"browser": {
"./server.js": "./server.browser.js",
"./unstable-fizz.js": "./unstable-fizz.browser.js"
},

In such cases i.e. when referenced module doesn't exist makeResolver function breaks.

Use @parcel/logger instead of console

As I use in my own plugin, there is a package @parcel/logger that integrates directly with Parcel's logging system. I would suggest using it instead of console because it looks much cleaner (you can install that plugin to see for yourself). You could also throw errors for prettier output than console.error.

I have a declaration file for @parcel/logger, which should give you a general sense of its API.

Parcel v2

Is this plugin compatible with Parcel v2 beta?

I tried every combination of:

{
	"peerDependencies": {
		"jquery": "*",
		"jquery.js": "*",
		"bootstrap": "*"
	},
	"externals": [
		"jquery => jQuery",
		"jQuery => jquery",
		"jquery => require('jquery')",
		"jQuery => require('jquery')",
		"jQuery => require('node_modules/jquery/dist/jquery.js')",
		"jquery => require('node_modules/jquery/dist/jquery.js')",
		"$ => require('node_modules/jquery/dist/jquery.js')"
	],
}

Importing like that:

import $ from 'jquery';

But jQuery is bundled into my app.js whatsoever :-(

Question: Is there a way to set all modules to be externals?

I am currently working with webpack and considering looking into adoption of parcel for a variety of reasons. So far, externals seems to be the primary feature that is missing when considering this adoption. We need to leverage externals in two different ways. I wanted to know if this plugin currently supports our two use cases and, if not, whether it would be possible to add this support.

First use case: typical webpack externals. This is the normal use case and the one I am fairly certain is supported. In a situation where I am developing a single page application and I know that jQuery will be available globally I can list it as an external and not include it in the application's bundle.

Second use case: marking all node_modules as externals. This is the use case I am not as confident is supported. We develop a number of internal libraries and we know for a fact that the dependencies of those libraries will be downloaded by the applications that consume those libraries. Therefore, whenever we bundle a library, we list all of its dependencies as externals using a library designed for webpack - webpack-node-externals. Is it possible to mimic this behavior using this plugin?

Can't tell if plugin is running

I am using Parcel 1.12.4 which is installed globally.
I installed this plugin globally
npm install -g parcel-plugin-externals

I ran into this error message:
'Error: Cannot find module 'parcel-bundler/src/Logger'

But I think I resolved it by adding some missing files as per
https://stackoverflow.com/questions/62870976/parcel-plugin-transcrypt-fails-with-error-cannot-find-module-parcel-bundler-s

I added the following to my package.json but I am not confident that I did it correctly.

 "externals": [
    "userConfig"
  ],
  "alias": {
    "userConfig": "../config/user_config.js"
  }

When I run my parcel build I can not see if the plugin is working, but my desired end result is definitely not working.

I think I have an incredibly simple use case. I have an external file that I want to import manually and I want Parcel to ignore it. I want my import statement to remain exactly as it is:
At run time if I perform
import('../config/user_config.js')

I want it to read that actual file on in that location and not be redirected to some bundled code.

Am I asking too much?

Exception on browser property set to false

The package.json browser field allows properties to be set to false to exclude files from bundling. However, this causes parcel-plugin-externals to crash as resolve does not accept values other than strings.

Help how to 'externalize' all of WordPress' globals

2 questions for a situation where my app loads within WordPress and therefore globals like jQuery, Moment, Lodash, React, and ReactDOM are already...

References:

  1. What does "*" actually do?
  2. Why doesn't lodash.isequal get excluded from my own build?

Any and all suggestions for my "externals" are welcome! :D

===

Here's a Parcel build report:

image

Here's an example npm ls ...:

image

Here's my externals:

{
...
"externals": {
	"@babel/runtime/regenerator": "regeneratorRuntime",
	"@wordpress/element": "wp.element",
	"jquery": "jQuery",
	"lodash": "lodash",
	"lodash-es": "lodash",
	"moment": "*",
	"react": "React",
	"react-dom": "ReactDOM"
}
...
}

Support for fake modules?

I am writing a VSCode extension that imports the vscode module for interfaces and other things. However, this module does not exist and is provided on the fly by the editor, so there is an error when trying to build the extension. Does this plugin support scenarios like this?

Here is the project: https://github.com/arnohovhannisyan/vscode-syncify.

I tried adding the vscode module to both externals and peerDependencies but it didn't work.

How use parcel-plugin-externals properly in development/production mode ?

When I run the script:

  • " parcel build src/index.html " to build on production, the externals ones are not bundled. Good.

  • "parcel src/index.html" to develop, the external ones are NOT bundled but I need them in dev mode. ๐Ÿ˜ฅ

How can I prevent this behavior? I need those modules to develop

I tried to do

NODE_ENV=production
NODE_ENV=development

the externals modules are included in package.json into "externals" array.

Let me know if you need any more details

Thanks

(Node 8.16.x) SyntaxError: Unexpected token { ...

I'm seeing this error in version 0.4.1 and node 8.16.x:

  Plugin parcel-plugin-externals failed to initialize: /foo/node_modules/parcel-plugin-externals/utils.js:137
  } catch {
          ^

SyntaxError: Unexpected token {
    at NativeCompileCache._moduleCompile (/foo/node_modules/v8-compile-cache/v8-compile-cache.js:242:18)
    at Module._compile (/foo/node_modules/v8-compile-cache/v8-compile-cache.js:186:36)
...

Looks like that should be catch () {.... Works in Node 10.16.x.

Broken output with "--bundle-node-modules" option

Env:

"dependencies": {
  "aws-sdk": "2.585.0"
},
"externals": {
  "aws-sdk": "aws-sdk"
},
"devDependencies": {
  "@types/aws-lambda": "^8.10.47",
  "@types/node": "^13.9.8",
  "parcel-bundler": "^1.12.4",
  "parcel-plugin-externals": "^0.3.3-pre.20200323.2",
  "typescript": "^3.8.3"
},
"engines": {
  "node": ">=12.0.0 <13.0.0"
}

Code written in Typescript.

What I wanted to achieve:

Generally bundle all node_modules except a few external ones ("aws-sdk" for a lambda in my case).

Problem I encountered:

My index.ts looks something like this:

import { Handler } from 'aws-lambda'; // Types only "@types/aws-lambda" in devDependencies
import { Athena } from 'aws-sdk';
import { format } from 'util';
// Other code imports local to src folder [...]

export const handler: Handler = async (event, context) => {
  const athenaService = new Athena();

  //...

  return format('%s Done', 'Really');
};

When I compile this normally with e.g.
parcel build ./src/index.ts --out-dir dist --out-file index.js --global handler --target=node --experimental-scope-hoisting --no-source-maps --no-content-hash
it works correctly and the require('aws-sdk') is correctly left in the output.

But if I compile it with the --bundle-node-modules it breaks and the output contains something like this:

var k={};k=aws-sdk;

also the parcel output shows:

dist/index.js                                1.33 KB    1.38s
dist/aws-sdk => aws-sdk.5848e953.external        0 B     26ms

In both cases the require('util') is present in the output as it should.

How to alias React and ReactDOM as `wp.element`?

Continuing from parcel-bundler/parcel#144 (comment)

Maybe I'm not doing it just right or not understanding how things work, but here's what I've got so far:

The issue here is that WordPress is already loading React and ReactDOM via the @wordpress/element package from yoursite-com/wp-includes/js/dist/element.js (WordPress core, not my own path, as you can see).

Here's my package.json:

{
	...
	"dependencies": {
		"react-notifications-component": "^2.3.0"
	},
	"devDependencies": {
		"@babel/core": "^7.8.7",
		"@wordpress/scripts": "^7.1.2",
		"npm-run-all": "^4.1.5",
		"parcel-bundler": "^1.12.4",
		"parcel-plugin-externals": "^0.3.3",
		"postcss-modules": "^1.5.0"
	},
	"babel": {
		"presets": [
			"@wordpress/default"
		]
	},
	"browserslist": [
		"extends @wordpress/browserslist-config"
	],
	"externals": "./.externals.js"
}

Here's the .externals.js that my package.json runs:

const rxWp = /node_modules\/@wordpress\/(.*?)\//;
const rxBabel = /node_modules\/@babel\/(.*?)\//;
const rxReact = /node_modules\/react-(.*?)\//;

module.exports = function( path ) {
	const wp = rxWp.exec( path );
	const babel = rxBabel.exec( path );
	const react = rxReact.exec( path );

	let wpSuffix;

	let babelSuffix;

	let reactSuffix;
	let reactName;

	if ( wp ) {
		wpSuffix = wp[ 1 ];
		return `@wordpress/${wpSuffix} => require('@wordpress/${wpSuffix}')`;
	}

	if ( react ) {
		reactSuffix = react[ 1 ];
		reactName = reactSuffix[ 0 ].toUpperCase() + reactSuffix.substr( 1 );
		return `react-${reactSuffix} => React${reactName}`;
	}

	if ( babel ) {
		babelSuffix = babel[ 1 ];
		return `@babel/${babelSuffix} => require('@babel/${babelSuffix}')`;
	}

	return undefined;
};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.