sveltejs / sveltejs/kit

Building multiple permutations of bundle

Open
#9,407 1 comment 23 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

size:large
Dominant language
JavaScript
Stars
20.8k
Forks
2.3k
Avg merge
1d 16h
Merged PRs (30d)
156

Description

Describe the problem

Concept

A permutation is a way to build multiples variants of the same Svelte application, according to various criteria.

A permutations will have an impact on the generated code by allowing certain files to be 'replaced' by others at build time.

This will allow to have a common codebase for multiple target, each of which could have its own specificities, like dedicated CSS/components/code/ressources/pages...

Some use-case :

  • Sites for different audiences.
  • Sites for different localisations.
  • Sites for different clients, with specific template/customisation.
Describe the proposed solution

Permutations keys

Permutations would be defined by custom keys and their values, declared on the svelte.config.js

/// svelte.config.js
permutations: {
	keys: {
		/* any records as name: string[] */
	}
},

Permutation keys will be project-specific key/values that define variants of our project.

For example, if we want to use a common project to build 3 specifics site/app for our customers (b2c), our partners (b2b), and our internal teams (int), we could define something like this :

/// svelte.config.js
permutations: {
	keys: {
		target: ['b2c', 'b2b', 'int']
	}
},

And if, in addition to that, we want to generate 4 differents languages ('en', 'fr', 'de', 'it'), we could use :

/// svelte.config.js
permutations: {
	keys: {
		target: ['b2c', 'b2b', 'int'],
		lang: ['en', 'fr', 'de', 'it' ]
	}
},

We have now 3 targets variants in 4 differents langs, which will produce 12 distincts permutations builds :

  • b2c,en
  • b2c,fr
  • b2c,de
  • b2c,it
  • b2b,en
  • b2b,fr
  • b2b,de
  • b2b,it
  • int,en
  • int,fr
  • int,de
  • int,it

By default, each build will have his own subdirectory in the svelte-kit folder :

.svelte-kit/
    b2c,en/
        generated/
        output/
        types/

    b2c,fr/
        generated/
        output/
        types/

    ...

    int,de/
        generated/
        output/
        types/

    int,it/
        generated/
        output/
        types/

And all npm run command will use the first-permutation ('b2c,en' in the current case) :

# Launch dev mode for permutation 'b2c,en' (default)
npm run dev

But we might use a flag to select another permutation, for example --perm :

# Launch dev mode for permutation 'b2c' in 'fr' (explicitly)
npm run dev --perm b2c,fr
# Launch dev mode for permutation 'int' in 'it' (explicitly)
npm run dev --perm int,it

Special case : the check and build task will accept an --all-perms flag to check/build all the permutations :

# Launch check for all permutations
npm run check --all-perms
# Launch build for all permutations
npm run build --all-perms

Permutations files

All files of the project will accept a permutation rules in order to choose the more specific file.

This could be represented by a mark as an extension suffix like .(permutations), juste before the real file extension.
For example for the logical file mycode.js, we can have :

  • mycode.(b2c,en).js which represents the file for the 'b2c,en' permutation.
  • mycode.(b2c).js which represents the file for any of the 'b2c' permutations.
  • mycode.(en).js which represents the file for any of the 'en' permutations.
  • mycode.js which represents the default file, when any other match.

By using unique permutation name and some naming convention, we can even make more precise targeting.
Ex:

  • mycode.(b2c,b2b).js represents all the permutations with target 'b2c' or 'b2b'.
  • mycode.(b2c,b2b,en).js represents all the permutations with target 'b2c' or 'b2b', AND the lang 'en'.
  • mycode.(-int).js represents all the permutations where target is NOT 'int'
Import file

The permutation should work when we import a file, for example :

<script>
	import Header from './Header.svelte';
	import Footer from './Footer.svelte';
	import messages from './messages.js';
	import './styles.css';
</script>

<Header />
<slot/>
<Footer />

The importeds files will depends from the current permutation and the permutations rules of the file's variants.

For example, if we have theses files :

	Header.(int).svelte
	Header.(b2b).svelte
	Header.(b2c).svelte

	Footer.(b2c,fr).svelte
	Footer.(b2c).svelte
	Footer.(fr).svelte
	Footer.(it).svelte
	Footer.svelte

	messages.(en).js
	messages.(fr).js
	messages.(de).js
	messages.(it).js

	styles.(b2c).css
	styles.(b2b).css
	styles.(intra).css

For permutation 'b2c,en', it will use Header.(b2c).svelte, Footer.(b2c).svelte, messages.(en).js and styles.(b2c).css
For permutation 'b2c,fr', it will use Header.(b2c).svelte, Footer.(b2c,fr).svelte, messages.(fr).js and styles.(b2c).css
For permutation 'b2b,en', it will use Header.(b2b).svelte, Footer.svelte, messages.(en).js and styles.(b2b).css
For permutation 'b2b,fr', it will use Header.(b2b).svelte, Footer.(fr).svelte, messages.(fr).js and styles.(b2b).css
...

The compiler should produce an error if the permutation mark is used on import :

// ERROR : invalid import with a permutation mark
// Should use 'Header.svelte' instead
import Header from 'Header.(b2c).svelte';
Static file

The same rule should apply to the static file.
For example in order to have distinct favicons :

   favicon.(b2b).png
   favicon.(b2c).png
   favicon.(int).png

When serving/building the apps, the file will be renamed with his base name.

Routing

The svelte routing files (+page, +layout, +server) should follow the same rule, with one exception : a missing permutation will not throw an error, but simply consider that the file does not exist.

Example, with this with this route tree :

routes/
     stuff/
         +page.svelte
     news/
         +page.svelte
         +page.(int).svelte
     contact/
         +page.(b2c).svelte
         +page.(b2b).svelte
     about/
         +page.(b2c,fr).svelte
         +page.(b2c,b2b).svelte
     +layout.(b2c).svelte
     +layout.(b2b).svelte
     +layout.(int).svelte
  • The 'stuff' page will be present on all permutations.
  • The 'news' page will be present on all permutations, but the 'int' permutations will use a specific template.
  • The 'contact' page will only be present on 'b2c' and 'b2b' permutations, with a specific template for each target.
  • The 'about' page only be present on 'b2c' and 'b2b' permutations, with a specific template for the 'b2c,fr' permutation.
  • Each target ('b2c', 'b2b' and 'int') will use a specific layout template.

Permutations directories

Another solution will be to use a permutation directory for src and static.
For example :

   src/               # Will contains code shared by all permutations.
   src.(b2c)/         # Will contains code for 'b2c' permutation.
   static/            # Will contains static file for all permutations.
   static.(b2c)/      # Will contains static file for 'b2c' permutation.

Perhaps we can event use both, so :

import Component from '$lib/Component.svelte';

For the b2c,en permutation, it will return the first file found in this order :

  • src.(b2c)/lib/Component.(en).svelte or src/lib/Component.(b2c,en).svelte
  • src.(b2c)/lib/Component.svelte or src/lib/Component.(b2c).svelte
  • src.(en)/lib/Component.svelte or src/lib/Component.(en).svelte
  • src/lib/Component.svelte

By generating an error in case of duplicate file at the same level.

Server side processing

The handle() hook in hooks.server.js|ts will receive a new property 'permutation'.
This property will be a object representing the current permutation configuration.

For example, for the b2c,en permutation, this will match an object of the following form :

{
    target: 'b2c',
    lang: 'en'
}

Build mode

The build mode will define the way that Sveltekit will build the apps.
I see two main build mode.

build: 'distinct'
/// svelte.config.js
permutations: {
	keys: {
		target: ['b2c', 'b2b', 'int'],
		lang: ['en', 'fr', 'de', 'it' ]
	},
	build: 'distinct'
},

In 'distinct' build mode, Sveltekit wil produce a distinct build for each permutation.
This is the default behavior described since the beginning of this document.

It should be compatible with all current adapters.
We just need to specify the build we want to dev/build on the npm run commands.

build: 'bundle'
/// svelte.config.js
permutations: {
	keys: {
		target: ['b2c', 'b2b', 'int'],
		lang: ['en', 'fr', 'de', 'it' ]
	},
	build: 'bundle'
},

In ‘bundle’ build mode, Sveltekit wil produce a common build for all the permutations.
This build will include all the permutation code, and will generate an app.js for each permutation, with it's own dictionary.

The server will serve the correct client code based on the request.
Obviously this mode will be incompatible with some adapters without server-side code (like adapter-static)
In order to do that, it will require a mapping property that define all the mapping between the URL and the permutations.

For example :

/// svelte.config.js
permutations: {
	keys: {
		target: ['b2c', 'b2b', 'int'],
		lang: ['en', 'fr', 'de', 'it' ]
	},
	build: 'bundle',
	mapping: {
		'www.my-site.com': 'b2c,en',
		'www.my-site.fr': 'b2c,fr',
		'www.my-site.de': 'b2c,de',
		'www.my-site.it': 'b2c,it',
		'www.my-site.pro/en': 'b2b,en',
		'www.my-site.pro/fr': 'b2b,fr',
		'www.my-site.pro/de': 'b2b,de',
		'www.my-site.pro/it': 'b2b,it',
		'admin.my-site.com/en': 'int,en',
		'admin.my-site.pro/fr': 'int,fr',
		'admin.my-site.pro/de': 'int,de',
		'admin.my-site.pro/it': 'int,it',
	}
},

Here www.my-site.com will serve the 'b2c,en' permutation, www.my-site.fr will serve 'b2c,fr', and so on...

We can also imagine a mapping_dev allowing to use different domain for dev mode.

An alternative/complement would be to use a server hook for that, allowing us to handle more special case :

/// hooks.server.ts
export  function  get_mapping(url: URL) {
	switch (url.hostname) {
		case  'www.my-site.com': return  'b2c,en';
		case  'www.my-site.fr': return  'b2c,fr';
		case  'www.my-site.de': return  'b2c,de';
		case  'www.my-site.it': return  'b2c,it';
		...
	}
	return  null;
}

In case of null/incorrect permutation name, the server will return an error.
And in this case we can use the handle() to redirect to the best domain...

build: 'group'
/// svelte.config.js
permutations: {
	keys: {
		target: ['b2c', 'b2b', 'int'],
		lang: ['en', 'fr', 'de', 'it' ]
	},
	build: 'group',
	group_keys: ['target']
},

The ‘group’ build mode is an intermediate version.
Sveltekit will generate distinct build based on the specified group_keys, which will bundle the other permutation keys.

In this example, using group_keys: ['target'] will generation 3 main permutations :

  • b2c that bundle b2c,en, b2c,fr, b2c.de and b2c.it
  • b2b that bundle b2b,en, b2b,fr, b2b.de and b2b.it
  • int that bundle int,en, int,fr, int.de and int.it
Alternatives considered

No response

Importance

nice to have

Additional Information

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the proposed svelte.config.js permutations settings and the interactions with src, static, routing files, hooks.server.js|ts, and the npm dev/check/build commands. This proposal spans file resolution, static assets, server mapping, and distinct, bundle, and group builds; work is done only after those behaviors and their scope are defined and agreed.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
build-system, web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
18/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.