UNPKG

fuse-box

Version:

Fuse-Box a bundler that does it right

369 lines (254 loc) 12.3 kB
# Bundle Bundling in FuseBox starts with the basic configuration. All bundles within the same scope will share the same config but can be overridden. steps: * Bundle producer A producer is a master configuration that retains a context, plugins and other settings * Config inheritance Each bundle inherits configuration except for homeDir and output * Run After you bundles are defined you can execute fuse.run() which will execute them all. A producer knows about the order, in a way it's an orchestrator The following diagramm illustrates the process: ``` graph TB main(Producer) main --> commonConfig(Common Configuration) commonConfig --> a(Bundle A) commonConfig --> b(Bundle B) a --> override(config override) b --> override2(config override) main --> run((Execute bundles)) ``` You can create infinite amount of bundles as well as infinite amount of `Producers`. ## Producer A parent configuration called `Producer`. It's where we define global configuration for all bundles. Some of the options, however, cannot be changed like `homeDir` and `output`. ```js const {FuseBox, EnvPlugin, CSSPlugin, UglifyJSPlugin} = require("fuse-box"); const production = false; const fuse = FuseBox.init({ homeDir: "src", output: "dist$name.js", hash: production, cache: !production, plugins: [ EnvPlugin({ NODE_ENV: production ? "production" : "development" }), CSSPlugin(), production && UglifyJSPlugin() ] }); ``` This is a pretty basic configuration that will work with typescript. `fuse` in our case if a `BundleProducer`. You can create as many bundles as you like. `production` variable enables hash and disables cache for production builds. note: It's important to disable cache for production builds. ## Creating a bundle Having your producer `fuse` in place this code will be enough to create your first bundle ```js const fuse = FuseBox.init({ homeDir: "src", output: "dist/$name.js", }); fuse.bundle("app") .instructions(`>app.tsx`); fuse.run() ``` `fuse.run()` should be executed once after all bundles are defined. `app` is bundle name - it will processed through `output: "dist/$name.js"` resulting in `dist/app.js` file. `app.tsx` should be place inside your `homeDir`. Following tree will help you better understand the structure: files: my-awesome-project node_modules placeholder.js src app.tsx fuse.js github_example: simple-bundle After the bundling is done, you will see similar output: ```bash └── default (3 files, 907 Bytes) index.js Bar.js Foo.js Size: 907 Bytes in 22ms ``` You can test the bundle on server by running `node dist/app.js` ## Arithmetic instructions FuseBox uses an arithmetic approach to bundling. ```js fuse.bundle("app").instructions(">app.tsx"); ``` * If you want a bundle to be executed on load, add `>` in front of your entry file. In case your bundle serves as an individual library, you would not want to make an automatic execution. * Make sure to keep the extension as you can point to a typescript file. * All *inputs* are relative to `homeDir` With arithmetic instructions, you can explicitly define which files go to the bundle, which files skip external dependencies e.g. ```js fuse.bundle("app").instructions(">index.ts [lib/**/*.ts]"); ``` In this case, you will get everything that is required in the index, as well as everything that lies under `lib/` folder with one condition - any external libraries will be ignored. ## Arithmetic Symbols | Symbol | Meaning | | ------------- | ------------- | | ` > ` | Automatically executes a file on load | | ` + ` | adds a package / file | | ` - ` | excludes a package / file | | ` ! ` | removes the loader API from a bundle | | ` ^ ` | disables cache | | ` ~ ` | Extract all external dependencies. Ignores the actual project files. Used to create vendors. `~ index.ts` | | ` [ ] ` | matches everything inside without dependencies | | ` **/*.ts ` | matches every file using globs, with dependencies, experiment with [globtester](http://globtester.com) | For most cases just pointing it your entry point will be enough, as FuseBox will walk recursively the dependency tree and bundle everything related ```js instructions(">index.ts"); ``` However, there are cases that require special treatment. For example files that don't have references need to be added manually ```js instructions(">index.ts + lib/**/**.ts"); ``` ## Creating vendors Creating vendors in FuseBox is extremely easy. All you need to is to add `~` this symbol to your entry point. ```js const vendor = fuse.bundle("vendor") .instructions(`~ **/**.{ts,tsx}`); if (!production) { vendor.hmr(); } ``` In this case every single `ts` and `tsx` modules will be processed, resulting in `vendor.js` that will contain all required dependencies. Actuall project files will be omited. ```js if (!production) { vendor.hmr(); } ``` Is a nice trick to avoid `hmr` related code in a production build. If you are not planning on having code splitting, having following will be enough: ```js fuse.bundle("vendor") .instructions(`~ index.ts`); ``` note: Don't forget to run the producer by adding fuse.run() at the very end of your script! Usually vendors contain the `FuseBox` api (The actual universal loader in browser and on server). Make sure every other bundle has `!` symbol in the arithmetic instructions. You don't want to have dependencies bundled either (in your `app.js`) ```js const fuse = FuseBox.init({ homeDir: "src", output: "dist/$name.js" }); fuse.bundle("vendor") .instructions(`~ index.ts`); fuse.bundle("app") .instructions(`!> [index.ts]`); fuse.run() ``` The code above will make sure that: steps: * Vendor vendor.js contains all project dependencies + the FuseBox API * Application app.js Contain only project files without dependencies (e.g react) and does not have FuseBox API (as vendor.js has it all). `[index.ts]` means that your bundle will contain everything related to `index.ts` without external dependencies like `react` or `angular` github_example: vendor-splitting You can now visit `http://localhost:4445` to see how it works ## Option override You can use the chainable API which will allow you to override options (shim, e.t.c). For example: ```js const fuse = FuseBox.init({ homeDir: "src", shim: { 'react-native-web': { exports: 'require("react-native")' } }, output: "dist/$name.js", plugins : [HTMLPlugin()] }); fuse.bundle("bundle1") .shim: {}, .instructions(`~ index.ts`); fuse.bundle("bundle2") .plugin(CSSPlugin()) .instructions(`~ index.ts`); fuse.run() > ``` In this case both `bundle1` and `bundle2` will inherit `homedir`, `shim`, `output`, and `plugins`. `bundle1` will override the `shim` option with an empty shim, and `bundle2` will add an additional plugin, so that `bundle2`'s plugins will be: `[HTMLPlugin(), CSSPlugin()]` ## Chainable API Everything bundle returns a chain. For example ```js fuse.bundle("app") .watch() .plugin(SassPlugin(), CSSPlugin()) .plugin(HTMLPlugin()) .hmr() .instructions(`~ index.ts`); ``` Available methods: | Name | Meaning | | ------------- | ------------- | | ` watch() ` | Automatically watches and reload a bundle. Read `development` section | | ` globals() ` | Set [globals](/page/configuration#global-variables) | | ` tsConfig() ` | Sets `tsconfig.json` location for typescript | | ` hmr() ` | Enables HMR. Read up [development](/page/development) | | ` alias() ` | Sets up [aliases](/page/configuration#alias) | | ` split() ` | Defines [code splitting](/page/code-splitting) rules. | | ` splitConfig() ` | Defines [code splitting](/page/code-splitting) configuration. | | ` cache() ` | Toggles cache | | ` log() ` | Toggles logging | | ` plugin() ` | Add a plugin or a chain of plugins | | ` natives() ` | Set [natives](/page/configuration#natives) | | ` instructions() ` | Defines [arithmetic instructions](/page/bundle#arithmetic-instructions) | | ` sourceMaps() ` | Toggles [sourcemaps](/page/configuration#sourcemaps) | | ` exec() ` | Executes a bundle individually from `fuse.run()` | | ` completed() ` | A callback when a bundle is ready. | ## Launching on server You can capture an event when a single bundle is completed. You will have access to `FuseProcess` that will help you to launch your application on server. ```js fuse.bundle("bundle2") .instructions(`~ index.ts`); .completed(proc => proc.start()) fuse.run() ``` ### Executing a bundle ```js completed(proc => proc.exec()) ``` The following code will spawn a separate nodejs process once. ### Start / restart ```js completed(proc => proc.start()) ``` The following code will spawn a separate nodejs process, if a process is already running FuseBox will kill and spawn a new one. ### Require ```js completed(proc => proc.require(opts)) ``` The following code will require a file in the same process as the fuse process instead of launching a new one. The differences are : * A bundle is executed in a `Promise` and its exports are available to the fuse caller : `proc.require().then(exports => void)`. * A bundle has access to the same loaded libraries than the fuser, they share the same global object. * A bundle is inspected if fuse is inspected: `node --debug fuse.js` debugs the bundle too. * To free the allocated resources when a bundle is restarted, there is no clean `process.kill` option; it must therefore export a `close` function, or a default that has such a function. An `express` bundle would look as follows: ```js export default app.listen(process.env.PORT); ``` github_example: recursive #### Options * `close(bundleExport)=> Promise`: A closing function. The exports of the main file can be retrieved with `bundleExport.FuseBox.import(bundleExport.FuseBox.mainFile)` #### Closing function When the module is unloaded, the first of these functions is called : * A function `close(bundleExport)=> Promise` given as an option to `require` After, if the bundle has a main file, * An `export function close(): Promise` in the bundle * A default export who has a `close()=> Promise` function. If the close function returns a promise, this one will be awaited before requireing the new version of the bundle. If it returns anything else than a promise, the value is ignored. The `require` function by itself returns a promise that resolves to the loaded bundle main-file `FuseBox` Object. ## Bundle in a bundle The super powers of FuseBox allow merging bundles inside of bundles without code redundancy. The API of a second bundle will be removed, and 2 bundles will be fused together, keeping only one shared Fusebox API. Only one thing you need to consider before that - packaging. Your current project is called "default" This is by design. All dynamic modules will register themselves into it automatically. If you want to require a bundle it must have a different namespace. Unless you want to keep it shared. Read up on [package naming](#package-name) for better understanding. Bundle your first package, then make sure your master/main bundle does not have the same package name (otherwise they will [share filename scopes](#scoping-fused)) and require it like any other file. ```js import * as myLib from "./bundles/myLib.js" ``` FuseBox sniffs its own creations and restructures the code accordingly. ## Importing Bundles you can import using the fusebox api wrapper that is built into the bundle ```js const bundled = require("./magic/yourOutFile.js") const exports = bundled.FuseBox.import("./yourBundle.js"); ``` or you can import the file directly using FuseBox ```js const bundled = require("./magic/yourOutFile.js") const bundled = FuseBox.import("./yourBundle.js") ``` ## Scoping / Fused If you have more than one bundle and require them, they will be `fused` behind the scenes. That is to say, they will be able to import from each other. This is possible because FuseBox is not just a bundler, but a full featured virtual environment! [See an example using fusing](https://github.com/fuse-box/fuse-box-scopes-example).