Skip to content

Building multiple permutations of bundle #9407

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

Open
adiguba opened this issue Mar 11, 2023 · 1 comment
Open

Building multiple permutations of bundle #9407

adiguba opened this issue Mar 11, 2023 · 1 comment
Labels
size:large significant feature with tricky design questions and multi-day implementation

Comments

@adiguba
Copy link

adiguba commented Mar 11, 2023

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

@dummdidumm dummdidumm added the size:large significant feature with tricky design questions and multi-day implementation label Mar 13, 2023
@benmccann benmccann changed the title Permutations Building multiple permutations of bundle Apr 13, 2023
@probablykasper
Copy link

Some of this could also be accomplished with

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size:large significant feature with tricky design questions and multi-day implementation
Projects
None yet
Development

No branches or pull requests

3 participants