-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Parcel 2: multi entry/target builds #3302
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
Comments
👍 I'm happy to hear that you are looking into supporting different targets for different entries. I'm fine with either of the first two proposals. But I do have a slight preference to name entries, e.g. The WorkerFarm proposal sounds very interesting from an SSR perspective. When the user creates a new page const workerFarm = new WorkerFarm();
let serverParcel;
let browserParcel;
ssrCoin.onCreatedOrRemovedPage(({pageServerEntries, pageBrowserEntries}) => {
// `pageBrowserEntries` would be something like this:
// {
// first_pageBrowserEntry: '/path/to/my-ssr-app/.build/generated-source-code/first.page.js',
// second_pageBrowserEntry: '/path/to/my-ssr-app/.build/generated-source-code/second.page.js',
// }
// `pageServerEntries` would be something like this:
// {
// first_pageServerEntry: '/path/to/my-ssr-app/pages/first.page.js',
// second_pageServerEntry: '/path/to/my-ssr-app/pages/second.page.js',
// }
// (In order to minimize bundle size, `ssr-coin` generates the source code of
// the browser entry of each page. This is not necessary for the server;
// the page entries for the server are the `pages/*.page.js` files written
// by the user.)
if( browserParcel ) {
browserParcel.stop();
}
if( serverParcel ) {
serverParcel.stop();
}
browserParcel = (
new Parcel({
workerFarm,
entries: {
...pageBrowserEntries,
},
targets: {
browser: {
browser: ['>1%', 'not dead']
}
})
);
serverParcel = (
new Parcel({
workerFarm,
entries: {
...pageServerEntries,
serverEntry: '/path/to/server/start.js'
},
targets: {
node: {
node: ['^8.0.0']
}
}
})
);
browserParcel.start();
serverParcel.start();
});
workerFarm.onBuildEnd(assetGraph => {
// Note that it's convenient to have named the server entry `serverEntry`
startServer(assetGraph.serverEntry.bundlePath);
});
let serverProcess;
function startServer(serverEntryPath) {
if( serverProcess ){
serverProcess.kill();
}
serverProcess = fork(serverBuildEntry);
} It would be super convenient to be able to dynamically change the config of the parcel instances. I can't wait to use Parcel for ssr-coin :-). |
One thing to think about is our trend of implementing servers as reporters. An SSR server would need to know about both the client and server build, which seems like it would work best if we went with the first option. |
With the worker farm proposal, the SSR server would as well know about both the client and server build, correct? It seems to me that every thing achievable with the first two proposals, can also be achieved with the worker farm proposal. Or am I missing something? |
@padmaia I think an SSR server would typically be wrapping Parcel rather than run as a part of it, which means the wrapper could just run two separate Parcel instances. But if we wanted to support an SSR server running as a Parcel plugin, then yeah, it would need to know about both builds. |
Ok got it.
If the SSR server is running as a Parcel plugin, then the SSR server wouldn't know aout both builds. |
Yes, the thing is that the SSR server should be owned by the user. Currently, and AFAIK, the Parcel dev server is hidden from the user. What I'm doing is that I expose SSR as a middleware, for example with express: const express = require('express');
const ssr = require('ssr-coin');
const app = express();
app.use(ssr.express);
// There is also a middleware for Koa and Hapi I wouldn't know how to provide these middlewares when SSR is run as a Parcel plugin. If possible, I'd be up to implement a Parcel SSR plugin though. |
About the first example, with named entries, one thing that i find annoying is the repetition of configuration for each entry file. const parcel = new Parcel({
entries: {
browserEntry: [
'/path/to/browser/entry/of/page1.js',
'/path/to/browser/entry/of/page2.js'
],
serverEntry: '/path/to/server.js',
},
targets: {
browserEntry: {
"browsers": ["> 1%", "not dead"]
},
serverEntry: {
"node": ["^8.0.0"]
},
}
}); But yeah, ideally, the worker farm option would be the best thing to have anyways. |
Having named entries is convenient. For example: workerFarm.onBuildEnd(assetGraph => {
// Note that it's convenient to have named the server entry `serverEntry`
startServer(assetGraph.serverEntry.bundlePath);
}); How would you do this if the server entry wouldn't be named So maybe something like this then: const parcel = new Parcel({
entries: {
browserEntry_page1: '/path/to/browser/entry/of/page1.js',
browserEntry_page2: '/path/to/browser/entry/of/page2.js',
serverEntry: '/path/to/server.js',
},
targets: [
{
entries: [
'browserEntry_page1',
'browserEntry_page2',
],
targets: {
"browsers": ["> 1%", "not dead"]
}
},
{
entries: [
'serverEntry',
],
targets: {
"node": ["^8.0.0"]
}
}
]
}); Personally, I don't care much. I'm happy as long as I can have different targets for different entries and as long as I don't have to synchronize two concurrent Parcel builds. Named entries are just a slight preference on my side. |
I think you are mixing the examples together. The example i gave is just a modification of the first @devongovett presented to reduce config duplication. It doesn't involve Continuing on the So instead of listening on a const workerFarm = new WorkerFarm();
const browserParcel = new Parcel({
workerFarm,
entries: ['page1.js', 'page2.js']
targets: {
browser: {
browser: ['>1%', 'not dead']
}
}
});
const serverParcel = new Parcel({
workerFarm,
entries: ['server.js']
targets: {
node: {
node: ['^8.0.0']
}
}
});
browserParcel.on('buildEnd', () => {
// You know it's the browserParcel entry
})
serverParcel.on('buildEnd', () => {
// You know it's the serverParcel entry
})
I don't think it's possible with this to have your synchronization of two |
EDIT: This request was apparently fixed by #3583 It would also be nice to allow for multiple entries using globs via both the CLI and the API, Parcel 1 allowed for it.
This would be useful for users using tools that use Parcel 2 and wants to perform an action on new files/file deletion on a folder. If this is not an available option, the only way to implement this efficiently would be to use the Edit: It would also be nice to have a programmatic API to add/delete entries from a parcel instance possibly running(in watch mode). This would be really useful in my case, for my testing tool, which heavily uses Parcel. |
That's very much what I would want though. I can already have different targets for different entries by running a different Parcel instance for each target. Other than performance, it seems to me that the whole point of this ticket is to make it easier to have different targets for different entries. In my experience, the need to synchronize two concurrent builds is the biggest pain when implementing SSR. Also, I'm not sure what the benefit in the following would be: const browserParcel = new Parcel({
entries: browserEntries,
targets: {
browser: {
browser: ['>1%', 'not dead']
}
}
});
const serverParcel = new Parcel({
entries: serverEntries,
targets: {
node: {
node: ['^8.0.0']
}
}
});
browserParcel.on('buildEnd', () => {
// Why should I care *only* about the events of the browser build?
})
serverParcel.on('buildEnd', () => {
// Why should I care *only* about the events of the server build?
}) I don't see many use cases where someone would be interested in events of only a portion of the build. It seems to me that the following makes much more sense: const workerFarm = new WorkerFarm();
const browserParcel = new Parcel({
workerFarm,
entries: browserEntries,
targets: {
browser: {
browser: ['>1%', 'not dead']
}
}
});
const serverParcel = new Parcel({
workerFarm,
entries: serverEntries,
targets: {
node: {
node: ['^8.0.0']
}
}
});
// I want to be able to listen to the *global* state events of the build.
workerFarm.on('buildEnd', () => {
// The server code and the browser code are built
});
// I'm not familiar with workerFarm and I don't know if it makes sense
// to listen to events on workerFarm.
// I don't mind if it's `workerFarm.on('buildEnd'` or `parcel.on('buildEnd'`.
// All I want is to not have to synchronize stuff. Otherwise I'd have to synchronize the events, for example: const browserParcel = new Parcel({
entries: browserEntries,
});
const serverParcel = new Parcel({
entries: serverEntries,
});
browserParcel.on('buildStart', () => {
buildState.browser = {
isBuilding: true,
};
onStateChange();
});
browserParcel.on('buildEnd', ({err, assetGraph}) => {
buildState.browser = {
isBuilding: false,
err,
assetGraph,
};
onStateChange();
});
serverParcel.on('buildStart', () => {
buildState.server = {
isBuilding: true,
};
onStateChange();
});
serverParcel.on('buildEnd', ({err, assetGraph}) => {
buildState.server = {
isBuilding: false,
err,
assetGraph,
};
onStateChange();
});
let neverSucceded = true;
function onStateChange() {
if( (buildState.browser.isBuilding || buildState.server.isBuilding) ){
console.log('Building');
return;
}
if( !buildState.browser.err && !buildState.server.err ){
console.log('Built');
if( neverSucceded ) {
startServer();
} else {
restartServer();
}
neverSucceded = false;
restartBrowser();
} else {
console.error('Build failed');
}
if( buildState.browser.err ){
console.error(buildState.browser.err);
}
if( buildState.server.err ){
console.error(buildState.server.err);
}
} This synchronization work is not a show blocker but it is annoying and not particularly user friendly. I've built an SSR tool on top of webpack. My tool has to synchronize two concurrent webpack builds. The synchronization is a huge pain. In webpack's case the code above is just a tiny tip of the iceberg. It's not only about SSR tool authors. It's also about developers who want a custom SSR implementation. As a tool author, it's okay to use non-friendly APIs. But, as a developer working for a startup, time is more critical and API user-friendliness substantially more important. The most complex part of SSR is the building. When people ask whether they should implement SSR themselves, I recommend against it because SSR induces a considerably more complex build. If Parcel makes SSR easy to implement then many companies would be able to implement SSR themselves which would be wonderful. |
The core infrastructure for this is implemented in #3583. That PR supports targets per entry as resolved from package.json, but not in the Parcel API yet. I think this ticket is now a matter of deciding on an API for passing entries and targets into Parcel and then connecting that to the Target resolution infrastructure. |
I think I found an API that would suit everyone: const parcel = new Parcel({
entries: [
{
entry: '/path/to/page1.js',
target: 'universal',
},
{
entry: '/path/to/page2.js',
target: 'universal',
},
{
entry: '/path/to/server.js',
target: 'node',
},
],
targets: {
// *********************
// ** Parcel defaults **
// *********************
// Following the zero-config approach, Parcel provides defaults `browser`,
// `browserModern` and `node`.
// These defaults can be overwritten in `package.json#targets`
// or with the Parcel programmatic API.
browser: {
browsers: ["> 0.25%", "not dead"],
},
browserModern: {
browsers: ["last 2 version"],
},
node: {
nodejs: "^8.0.0",
},
// ********************
// ** Custom targets **
// ********************
// Custom targets defined by the Parcel user.
// We use an array for multi-bundle targeting.
// This generates two bundles: one that works in all browsers and
// another one that works only in modern browsers.
browserOldAndNew: [
'browser',
'browserModern',
],
// This generates ONE bundle.
// (I know that Parcel doesn't support isomorphic builds; this
// is just to showcase the API.)
isomorphic: {
browsers: ["> 0.25%", "not dead"],
nodejs: "^8.0.0",
},
// This generates TWO bundles.
// (Such `universal` target is what SSR tools usually do.)
universal: [
'browser',
'node',
],
},
}); I purposely didn't name the entries here: since an entry is always a file on the disk, we can simply take the absolute path of the entry file as name. For example:
I'd be happy to research sensible defaults for The only thing missing here is the ability to dynamically add/remove entries, which is crucial for SSR. But this could be solved by #3699 - [RFC] New plugin type |
I think it's worthwhile to consider having this supported in {
"source": "src/index.html", // default source
"browser": "dist/browser/index.html",
"ssr": "dist/ssr/index.mjs",
"targets": {
"browser": {
"node": ">=8.0.0"
},
"ssr" {
"source": "src/ssr.tsx", // overwrite source
"node": ">=12.0.0"
}
},
// Alternatively:
"source": {
"browser": "src/index.html",
"ssr": "src/ssr.tsx"
},
// Alternatively-er:
"sources": {
"browser": "src/index.html",
"ssr": "src/ssr.tsx"
}
} |
@brillout I'm sorry but that's a big no for me, this is just unnecessarily complex, const parcel = new Parcel({
entries: [
{
entry: '/path/to/page1.js',
target: ['browser', 'node'],
},
{
entry: '/path/to/page2.js',
target: ['browser', 'node'],
},
{
entry: '/path/to/server.js',
target: 'node',
},
],
targets: {
browser: {
browsers: ["> 0.25%", "not dead"],
},
node: {
nodejs: "^8.0.0",
}
}
}) Would serve the same purpose, while being more explicit and straightforward. I don't like the fact that you can reference targets, from targets, this makes you have to look back everytime you see a definition that use a reference. Edit 1: BTW, about your Edit 2: While we're at it, we could even remove some config duplication by allowing entries as array, but then i'm not sure that const parcel = new Parcel({
entries: [
{
entry: ['/path/to/page1.js', '/path/to/page2.js'],
target: ['browser', 'node'],
},
{
entry: '/path/to/server.js',
target: 'node',
},
],
targets: {
browser: {
browsers: ["> 0.25%", "not dead"],
},
node: {
nodejs: "^8.0.0",
}
}
}) |
@Banou26 I like your version, it's simpler 👍 About AFAICT, From an SSR perspective, Maybe Parcel should support both: (EDIT: Typo - I meant |
I find the following confusing: {
"entries": [
{
"entry": ["/path/to/page1.js", "/path/to/page2.js"],
"target": ["browser", "node"]
}
]
} Does this mean that there will be a single bundle with 2 entry points? Or that there will be 2 bundles with each having a single entry point? I know the answer but I'm not sure a Parcel beginner would. |
Actually, SSR: const parcel = new Parcel({
source: [ // Note how it's `source` not `entries`
{
entry: '/path/to/page1.js',
target: ['browser', 'node'],
},
{
entry: '/path/to/page2.js',
target: ['browser', 'node'],
},
{
entry: '/path/to/server.js',
target: 'node',
},
],
targets: {
browser: {
engines: {
browsers: ["> 0.25%", "not dead"],
}
},
node: {
engines: {
nodejs: "^8.0.0",
}
}
}
}) NPM package: //package.json
// In a zero-config way:
{
"source": "src/index.js",
"main": "lib/index.js" // The default target of `main` is `node`
} //package.json
// The default target can be overridden by explicitly
// setting the target:
{
"main": "lib/index.js",
"source": [
{
"entry": "./src/index.js",
"target": "nodeModern",
// `output` needs to be explicitly set equal to `main`.
// (Because defining `package.json#source` as an Array
// is an escape hatch to Parcel's zero-config.)
"output": "lib/index.js"
},
],
"targets": {
"nodeModern": {
"engines": {
"nodejs": ">=13.x"
}
}
}
} SPA: //package.json
{
"source": "src/index.html" // The default target is `browser`
// when the entry is an `.html`
// `package.json#main` has no semantics in the context of an SPA
} |
I don't agree with that, I think it's perfectly acceptable to do things like: {
"source": "src/index.html",
"main": "dist/index.html"
} I agree that I think this is a perfectly reasonable setup for an app that supports multiple source entry points which split out into different targets. {
"source": "src/index.html",
"dist:legacy": "dist/legacy/index.html",
"dist:modern": "dist/modern/index.html",
"dist:ssr": "dist/ssr/ssr.js",
"targets": {
"dist:legacy": {
"browsers": [">0.5%", "not dead"]
},
"dist:modern": {
"browsers": [">3%", "not dead"]
},
"dist:ssr": {
"node": ">=10",
"source": "src/ssr.js"
}
}
} I want to stick to Parcel using |
Have the existing For an app, the package.json might look like: {
"browserslist": {
"legacy": [">0.5%", "not dead"],
"modern": [">3%", "not dead"]
},
"engines": {
"node": "12.13.0"
},
"volta": {
"node": "12.13.0",
"npm": "6.12.1"
}
} A couple points on this:
I think enforcing static Using the above approach, the environment configuration for various targets is somewhat decoupled from bundler-specific implementation details on how those targets actually get compiled (e.g. entries, plugins, etc.) I think unique, named ids for browser targets is a reasonable interface (and one that already exists with browserlist) |
@rtsao Yeah, |
There are a lot of assumptions being made in this thread that go against what Parcel is trying to achieve.
|
I disagree with this. I think |
All that accomplishes is flipping Here is the equivalent code with {
"spa": "dist/browser/index.html",
"server": "dist/server/start.js",
"browserslist": [">= 1%"],
"engines": {
"node": [">=8.x"]
},
"targets": {
"spa": {
"source": "browser/index.html",
"node": false,
"browser": true
},
"server": {
"source": "server/start.js",
"node": true,
"browser": false
}
}
}
You could even simplify it by making use of existing patterns in the ecosystem: {
"main": "dist/server/start.js",
"browser": "dist/browser/index.html",
"source": "server/start.js",
"browserslist": [">= 1%"],
"engines": {
"node": [">=8.x"]
},
"targets": {
"browser": {
"source": "browser/index.html",
}
}
} |
Those fields have specified/implicit meaning to other tools:
All three are JS library targets because they are used to resolve |
I've seen them used for CSS before. I'm not going to try to find a quote now, but the npm team has said they intend it to be used for other languages. There are also several bundlers that can handle arbitrary languages as the One of the things I want to be careful with is taking Parcel "defaults" and making them "rules". We might treat |
It is all possible to override, I'm just not sure we should encourage it: {
"main": "dist/index.html",
"targets": {
"main": {
"context": "browser",
"isLibrary": false,
"includeNodeModules": true,
"engines": {
"browsers": ["> 1%"]
}
}
}
} |
If we have non JS scripts as
What's @brillout your argument about not needing multiple node targets is based on the assumption that you have control over your Node version. Not all hosts gives you the latest versions of node. For now my best take in all of this is the configuration I talked about in #3302 (comment) |
In the context of a library, the shared configs But, in the context of an application, there is no such standard and there are no shared configs // SPA + Node.js server
{
"spa": "dist/browser/index.html",
"server": "dist/server/start.js",
"targets": {
"spa": {
"source": "browser/index.html",
"browser": true,
"node": false
},
"server": {
"source": "server/start.js",
"browser": false,
"node": true
}
}
} Since there are no shared configs, why don't we just skip them and do something like this: // SPA + Node.js server
{
"source": [
{
"entry": "browser/index.html",
"outFile": "dist/browser/index.html"
"target": "browser"
},
{
"entry": "server/start.js",
"outFile": "dist/server/start.js"
"target": "node"
}
]
} This // Library
{
"source": [
{
"entry": "src/index.js",
"outFile": "dist/index.js",
"target": {
"context": "node",
"isLibrary": true,
"includeNodeModules": false,
// The zero-config setup would build for `node>=8.x`.
// Thanks to the `package.json#source` array we can override this.
"engines": {
"node": ">=13.x"
}
}
// ...
}
],
"main": "dist/index.js"
} The idea here is to go with a dual interface strategy:
A nice thing about the // An SSR tool
// How would you achieve something like this with the
// current v2 design? AFAICT it would end up super ugly.
const parcel = new Parcel({
source: [
// The user defines a `pages/index.html` as an
// HTML document wrapper for all pages.
{
"entry": "pages/index.html",
"target": "browser",
"outDir": "dist/browser/"
},
// We build the server.
{
"entry": "server/index.js",
"target": "node",
"outDir": "dist/nodejs/",
},
// We build the landing page for the browser (for hydration).
{
"entry": "pages/landing/index.page.js",
"target": "browser",
"outDir": "dist/browser/"
},
// We build the landing page for Node.js (for server-side HTML rendering).
{
"entry": "pages/landing/index.page.js",
"target": "node",
"outDir": "dist/nodejs/"
},
// Other pages
{
"entry": "pages/about/index.page.js",
"target": "browser",
"outDir": "dist/browser/"
},
{
"entry": "pages/about/index.page.js",
"target": "node",
"outDir": "dist/nodejs/"
},
// ...
],
}); Since shared configs don't make sense for the programmatic API, only the The case for Shared configs enable beautiful zero-config setups, as we can seen in the context of libraries. If Parcel introduces the new shared configs // SPA
{
"source": "src/index.html",
"staticDir": "dist/"
// (Parcel knows about `staticDir`: The default target is
// `package.json#browserlist` or `browser: [">= 0.25%"]`)
} // Node.js server
{
"source": "src/server.js",
"server": "dist/server.js"
// (Parcel knows about `server`: The default target is
// `package.json#engines.node` or `node: ">=8.x"`)
} // SPA + Node.js server
{
"source": {
"staticDir": "browser/index.html",
"server": "server/index.js"
},
"staticDir": "dist/browser/",
"server": "dist/server/index.js"
} These are as beautiful as our zero-config setup for libraries 😍. Also, another immediate benefit is that Other tools would eventually use these new fields as well. If the user wants full control, he can use the Why not
AFAIK, we all agree on this: // Node.js library
{
"source": "src/index.js",
"main": "dist/index.js"
} So, instead of introducing a second field In the end, we have a single I can't think of anything simpler than that. |
@Banou26 @jamiekyle-eb Multi Node.js targeting could be achieved with a {
"source": [
{
"entry": "src/index.js",
"outFile": "dist/modern.js",
"target": {
"engines": {
"node": ">=13.x"
}
}
},
{
"entry": "src/index.js",
"outFile": "dist/index.js",
"target": {
"engines": {
"node": ">=8.x"
}
}
}
]
} This is verbose because the goal of the
We are all seem to disagree with you. Would you mind elaborating? Personally, I can't fathom what your motivation is. That's how I see it:
|
We aren't going to change the Seems like the best solution to this is to make two packages, e.g. in a monorepo: a server package and a client package. Each package.json should define the targets it wants. Then, you can point parcel at both folders with package.json and it will build them both together. In many cases, this won't be necessary at all: you'll have the same entrypoint for both client and server and can simply have a single package with two targets. |
@jamiekyle-eb I'm sorry if my reply came across as offensive. That wasn't my intention. I'm sorry. The only thing I care about is the beauty of Parcel and its zero-config philosophy which I agree so much with. (I actually started to build a zero-config bundler before Devon created Parcel but I never came to finish it.)
Neat idea. And, from an SSR perspective, it should work. I'll try that for @parcel-ssr. Although, how would you define multi targets for // pages/package.json
{
"source": "**/*.page.js"
// `dist/browser/` is the static directory to be served
"pages-browser": "dist/browser/",
// `dist/nodejs/` is read by the SSR plugin
"pages-nodejs": "dist/nodejs/",
"targets": {
"pages-browser": {
"context": "browser",
},
"pages-nodejs": {
"context": "node"
}
}
} When doing SSR, the pages need two targets. I find it weird and counter-intuitive to use artificial AFAIK, most I'm wondering if it wouldn't be prettier to have something like this: // pages/package.json
{
"source": "**/*.page.js"
"targets": [
{
"context": "browser",
"outDir": "dist/browser/"
},
{
"context": "node"
"outDir": "dist/nodejs/"
}
]
} I'm only speaking of my personal and limited perspective, I surely am missing many aspects and use cases. And I know that you don't want to change the And, any thoughts on introducing new shared configs // browser/package.json
{
"source": "index.html",
"staticDir": "dist/"
// (Parcel knows about `staticDir`: The default target is
// `package.json#browserlist` or `browser: [">= 0.25%"]`)
} // server/package.json
{
"source": "start.js",
"server": "dist/start.js"
// (Parcel knows about `server`: The default target is
// `package.json#engines.node` or `node: ">=8.x"`)
} I'm currently experimenting and implementing a |
yes
Yeah, the
It's not necessarily Parcel specific. Other tools can also read the metadata for the custom |
Interesting. Was the discussion public; can I read about it? I searched but couldn't find any public discussion.
It seems that
It however doesn't say what the target means. But I guess a tool could infer the meaning. {
"custom-target": "dist/server.js",
"targets": {
"custom-target": {
"context": "node"
}
},
"dependencies": {
"nodemon": "^1.2.3"
}
} Since For what it's worth, I still find that a To me, a beautiful design is mostly about 1. simplicity and 2. clear communication. I'd say that
is both crystal clear as well as super simple. I can't think of any argument against We could have Just my 2 cents. Thanks for the reply and I'll experiment all that with parcel-ssr. Super eager to do SSR with Parcel. Thanks for having created Parcel, it's beautiful. |
You can build that yourself though! {
"server": "dist/server.js",
"targets": {
"server": {
"context": "node",
"engines": {
"node": "12.x"
}
}
}
} |
I know and, as a tool author, this is perfectly fine. But, as a Parcel end-user, I'm a little bit saddened that Parcel doesn't introduce new semantics for Btw., what is the zero-config story going to look like for an SPA? Like the following? {
"source": "src/index.html",
// We cannot use `package.json#browser` since it's already used by libraries.
"spa": "dist/index.html",
"targets": {
"context": "browser"
}
} Hm... that still leaves me wondering why not this: {
"source": "src/index.html",
"staticDir": "dist/"
// Parcel knows that `package.json#staticDir` denotes the directory
// holding all static assets for the browser; the default target is
// `package.json#browserlist || [">= 0.25%"]`
} Sorry to be pushy, it's just difficult to abandon beauty 😊. I have some thoughts about Parcel's programmatic API that I'll write down in this ticket in the next coming days. |
To build an SPA with parcel, you can just do this {
targets: {
// or however the target is configured
spa: {
browsers: ["> 1%", "not dead"]
}
},
scripts: {
spa: "parcel --target=spa src/index.js"
}
} What's wrong with this way ? I mean, an SPA is like whatever else browser script, there's nothing that change. |
About the programmatic API: it would be absolutely wonderful if Parcel supports dynamic configurations. It seems that Parcel v2 watches const parcel = new Parcel();
parcel.config = {
source: [
{entry: '/path/to/pages/landing/Landing.page.js'},
{entry: '/path/to/pages/about/About.page.js'},
],
};
await parcel.build();
// We later add a new entry:
parcel.config.source.push({entry: '/path/to/pages/contact/Contact.page.js'});
// Parcel automatically rebuilds. That would enormously help tool authors. My current SSR tool (Goldpage), which is implemented on top of Webpack, needs to manually stop and restart Webpack and that's a huge pain. Would be lovely if I can simply dynamically change the config instead. AFAICT, watching a JS object should be possible by using a recursive proxied object. I can implement a POC, if you guys are interested. @Banou26 "scripts": {
"build": "parcel"
},
"dependencies": {
"parcel": "2.0.0"
}
I agree and I'm actually trying to do something like Next.js but as a Parcel plugin: github.com/brillout/parcel-ssr. But for this to work it seems that Parcel will need to implement changes (parcel/issues/created_by/brillout) and I'm not sure if Parcel is willing to implement these changes I need. If not then there is no other choice for me (and for any other SSR tool author) than to use Parcel with a programmatic API. |
i wonder what's the final MPA entries format? i could not start my parcel tasks with programmatic config below: // import {
// Parcel
// ,ConfigProvider
// } from '@parcel/core'
const Parcel = require('@parcel/core').default
console.info(Parcel)
const pkgConfig = require('../package.json')
console.info(pkgConfig.source)
const entries = [
...require('glob').sync('./src/**/*.pug')
]
console.info(JSON.stringify(entries, null, 4))
const browsers = [
"> 8%",
"not ie < 10",
"iOS > 8",
"Android >= 4.2"
]
const parcel = new Parcel({
entries
,targets: {
index: {
browsers
}
,browser: browsers
}
,buildStart (eve) {
console.info(101, eve)
}
,buildProgress (eve) {
console.info(302, eve)
}
,buildSuccess (res) {
console.info(200, res)
}
,buildEnd (res) {
console.warn(502, res)
}
,watch: true
,serve: true
,cacheDir: '.cache'
,logLevel: 3
,target: 'browser'
// ConfigProvider
// ,config: {
// }
})
console.info(parcel)
parcel.run() but only got |
What's the status of multi-entry builds from package.json for SSR in Parcel 2, current alpha? I tried all possible combinations from this thread and elsewhere and could not try it out :( |
@ricardobeat Still in the to do list https://github.com/parcel-bundler/parcel/projects/5#card-24711928 |
@ricardobeat This merge now makes any combination of multiple entries and multiple targets possible: Just add a |
Split out of #2574.
For SSR, it would be useful to support multiple targets with different entries simultaneously.
Three options came up in the previous issue:
Explicit entries per target
An array of parcel options
Multiple Parcel instances, sharing a worker farm
We were thinking of making the worker farm an option anyway, so this might work for free. By sharing a worker farm instance, multiple Parcel instances could run in parallel.
Thoughts?
cc. @brillout @padmaia @wbinnssmith
The text was updated successfully, but these errors were encountered: