├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── docs
├── circular.md
├── cli.md
├── compiler.md
├── dev-middleware.md
├── dev-server.md
├── hmr.md
├── live-bindings.md
├── nollup-hooks.md
├── nolluprc.md
├── options.md
├── plugins.md
├── rollup-config.md
└── sdk.md
├── examples
├── README.md
├── example-amd
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ ├── my-external-module-dynamic.js
│ │ └── my-external-module.js
│ ├── rollup.config.js
│ └── src
│ │ ├── dynamic-import-main.js
│ │ ├── multiple-export-main.js
│ │ ├── my-dynamic-module.js
│ │ └── single-export-main.js
├── example-circular
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ ├── A1.js
│ │ ├── A2.js
│ │ ├── A3.js
│ │ ├── B.js
│ │ └── main.js
├── example-dynamic-import
│ ├── .babelrc
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── Counter.css
│ │ ├── Counter.js
│ │ └── main.js
├── example-emit-chunk
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ ├── main.js
│ │ ├── shared.js
│ │ ├── worker-color.js
│ │ └── worker-size.js
├── example-globals
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ └── main.js
├── example-https
│ ├── .gitignore
│ ├── cert
│ │ ├── example.crt
│ │ └── example.key
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ └── main.js
├── example-live-bindings
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ ├── counter.js
│ │ └── main.js
├── example-mjs-config
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.mjs
│ └── src
│ │ └── main.js
├── example-multi-bundle
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ ├── entry-a.js
│ │ ├── entry-b.js
│ │ ├── message-a.js
│ │ └── message-b.js
├── example-preact
│ ├── .babelrc
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── Counter.css
│ │ ├── Counter.js
│ │ ├── Internal.js
│ │ ├── Switch.css
│ │ ├── Switch.js
│ │ └── main.js
├── example-public-path
│ ├── .babelrc
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── Counter.css
│ │ ├── Counter.js
│ │ └── main.js
├── example-react-esinstall
│ ├── .babelrc
│ ├── .gitignore
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── Counter.css
│ │ ├── Counter.js
│ │ ├── Internal.js
│ │ └── main.js
├── example-react-hot-loader
│ ├── .babelrc
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── Counter.css
│ │ ├── Counter.js
│ │ ├── Internal.js
│ │ ├── Switch.css
│ │ ├── Switch.js
│ │ └── main.js
├── example-react-refresh
│ ├── .babelrc
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── Counter.css
│ │ ├── Counter.js
│ │ ├── Internal.js
│ │ └── main.js
├── example-single-file-bundle
│ ├── .gitignore
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ └── src
│ │ ├── main.js
│ │ └── message.js
├── example-typescript
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.ts
│ └── src
│ │ ├── enum.ts
│ │ └── main.ts
└── example-virtual-index-html
│ ├── .gitignore
│ ├── package.json
│ ├── rollup.config.js
│ └── src
│ ├── main.css
│ └── main.js
├── jsconfig.json
├── lib
├── cli.js
├── dev-middleware.js
├── dev-server.js
├── impl
│ ├── AcornParser.js
│ ├── ConfigLoader.js
│ ├── NollupCodeGenerator.js
│ ├── NollupCompiler.js
│ ├── NollupContext.js
│ ├── NollupImportExportResolver.js
│ ├── NollupLiveBindingsResolver.js
│ ├── ParseError.js
│ ├── PluginContainer.js
│ ├── PluginContext.js
│ ├── PluginErrorHandler.js
│ ├── PluginLifecycle.js
│ ├── PluginMeta.js
│ ├── PluginUtils.js
│ ├── RollupConfigContainer.js
│ ├── types.js
│ └── utils.js
├── index.js
├── plugin-hmr.js
└── sdk.js
├── package.json
└── test
├── cases
├── DevMiddleware.js
├── ErrorHandling.js
├── Externals.js
├── ImportExportResolver.js
├── PluginHMR.js
├── RequireUsage.js
├── Scenarios.js
├── StaticDynamicImportOptimisation.js
├── VirtualModules.js
├── api
│ ├── context.js
│ ├── generate.js
│ ├── hooks.js
│ └── nollup_hooks.js
├── misc.js
└── options
│ ├── context.js
│ ├── input.js
│ ├── moduleContext.js
│ ├── output-assetFileNames.js
│ ├── output-chunkFileNames.js
│ ├── output-dir.js
│ ├── output-entryFileNames.js
│ ├── output-file.js
│ └── output-format.js
├── nollup.js
├── packages
├── circular-deep
│ ├── A.js
│ ├── B.js
│ ├── C.js
│ ├── index.js
│ └── message.js
├── circular-export-all-from
│ ├── A.js
│ ├── index.js
│ └── letters.js
├── circular-export-fn-as
│ ├── index.js
│ └── other.js
├── circular-export-from-infinite-loop
│ ├── A.js
│ ├── index.js
│ └── letters.js
├── circular-export-from
│ ├── A.js
│ ├── index.js
│ └── letters.js
├── circular-hoist-class
│ ├── index.js
│ └── other.js
├── circular-hoist-fn-require
│ ├── dynamic.js
│ ├── index.js
│ └── other.js
├── circular-hoist-var-patterns-extra
│ ├── index.js
│ └── other.js
├── circular-hoist-var-patterns
│ ├── index.js
│ └── other.js
├── circular-shared-import-timing
│ ├── index.js
│ ├── shared.js
│ └── two.js
├── circular
│ ├── A1.js
│ ├── A2.js
│ ├── A3.js
│ ├── B.js
│ └── index.js
├── empty-source-mapping
│ ├── content.json
│ └── index.js
├── export-all
│ ├── impl.js
│ ├── index-proxy.js
│ └── index.js
├── export-checks
│ ├── alias-dep-from.js
│ ├── dep-from.js
│ └── index.js
├── export-declaration-late-binding
│ ├── index.js
│ └── messages.js
├── export-default-from
│ ├── impl.js
│ └── index.js
├── export-full-live-bindings
│ ├── counter.js
│ └── index.js
├── export-import-delayed
│ ├── impl.js
│ └── index.js
├── export-same-export-as-from
│ ├── hello.js
│ ├── index.js
│ └── world.js
├── export-synthetic-all-from
│ ├── impl.js
│ └── index.js
├── hello-world
│ └── index.js
└── multi-module
│ ├── index.js
│ ├── message
│ ├── hello.js
│ ├── index.js
│ └── world.js
│ └── sum
│ ├── index.js
│ ├── one.js
│ ├── three.js
│ └── two.js
└── utils
├── evaluator-worker.js
├── evaluator.js
└── wait.js
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: CI
4 |
5 | # Controls when the workflow will run
6 | on:
7 | # Triggers the workflow on push or pull request events but only for the master branch
8 | push:
9 | branches: [ master ]
10 | pull_request:
11 | branches: [ master ]
12 |
13 | # Allows you to run this workflow manually from the Actions tab
14 | workflow_dispatch:
15 |
16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
17 | jobs:
18 | test:
19 | name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }}
20 | runs-on: ${{ matrix.os }}
21 | strategy:
22 | matrix:
23 | node_version: ['14']
24 | os: [ubuntu-latest, windows-latest]
25 |
26 | steps:
27 | - uses: actions/checkout@v1
28 | - name: Use Node.js ${{ matrix.node_version }}
29 | uses: actions/setup-node@v1
30 | with:
31 | node-version: ${{ matrix.node_version }}
32 |
33 | - name: npm install, build and test
34 | run: |
35 | npm install
36 | npm run test:mocha
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | dist/
3 | *.log
4 | node_modules/
5 | .DS_STORE
6 | *.swp
7 | .npm
8 | .eslintcache
9 | target/
10 | *.stackdump
11 | package-lock.json
12 | *.tgz
13 | NOTES
14 |
15 | coverage/
16 | TODO
17 | benchmark
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test/
2 | examples/
3 | NOTES
4 | example-project/
5 | package-lock.json
6 | target/
7 | .travis.yml
8 | API.md
9 | *.tgz
10 | coverage/
11 | docs/
12 | src/
13 | rollup.config.js
14 | jsconfig.json
15 | benchmark/
16 | TODO
17 | .github
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Paul Sweeney
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nollup
2 |
3 | 
4 | [](https://www.npmjs.com/package/nollup)
5 | [](./LICENSE)
6 | [](https://www.npmjs.com/package/nollup)
7 | [](https://github.com/PepsRyuu/nollup/graphs/contributors)
8 | [](https://twitter.com/PepsRyuu)
9 |
10 | ***No(t) Rollup → Nollup***
11 |
12 | [Rollup](https://rollupjs.org/guide/en) compatible bundler, ***designed to be used in development***. Using the same Rollup plugins and configuration, it provides a dev server that performs **quick builds and rebuilds**, and other dev features such as **Hot Module Replacement**.
13 |
14 | ## Why Nollup?
15 |
16 | Rollup is an incredible tool, producing very efficient and minimal bundles. Many developers use it already to build libraries, but I wanted to use it to **build apps**. However, **Rollup focuses mostly on the production** side of things, with almost no developer experience other than basic file watching. Using Rollup in development can be incredibly slow with rebuilds taking seconds because of all of the optimisations Rollup does for you (ie. tree-shaking, scope-hoisting).
17 |
18 | Nollup aims to fill in that gap. Using the same Rollup plugins and configuration, **you can use Nollup to run a development server that generates a development bundle**. It does no optimisations, making it really **quick at rebuilding**, also allowing for Hot Module Replacement using existing ```module.hot``` conventions for **compatibility with existing libraries**.
19 |
20 | Read further about why I prefer using Rollup to build apps [here](https://medium.com/@PepsRyuu/why-i-use-rollup-and-not-webpack-e3ab163f4fd3).
21 |
22 | ## How to Use
23 |
24 | Nollup provides four ways to use it:
25 |
26 | * [Nollup CLI](./docs/cli.md)
27 | * [Dev Server API](./docs/dev-server.md)
28 | * [Dev Middleware API](./docs/dev-middleware.md)
29 | * [Compiler API](./docs/compiler.md)
30 | * [SDK 🧪](./docs/sdk.md)
31 |
32 | For the majority of projects, it is recommended to use the CLI approach.
33 |
34 | ## Quick Start
35 |
36 | [create-nollup-app](https://github.com/PepsRyuu/create-nollup-app) is a CLI that will generate a Nollup project for you.
37 |
38 | ```
39 | // npm 6 and below
40 | npm init nollup-app --name --template
41 |
42 | // npm 7+ which requires extra dashes
43 | npm init nollup-app -- --name --template
44 |
45 | // any npm version
46 | npx create-nollup-app --name --template
47 | ```
48 |
49 |
50 |
51 | ## Examples
52 |
53 | The examples show different features of Nollup, including examples for React and Preact based projects with HMR. They also demonstrate how to use Nollup in development and Rollup to build production builds.
54 | Highly recommended to check them out [here](./examples).
55 |
56 | ## Hot Module Replacement
57 |
58 | See documentation about Hot Module Replacement [here](./docs/hmr.md).
59 |
60 | ## Supported Rollup Config Options
61 |
62 | See documentation about supported Rollup config options [here](./docs/rollup-config.md).
63 |
64 | ## Nollup Plugins
65 |
66 | Some Rollup plugins provide additional support for Nollup projects.
67 | You can find the list [here](./docs/plugins.md).
68 |
69 | ## Nollup Plugin Hooks
70 |
71 | Nollup provides additional plugin hooks for plugin authors to implement features such as HMR. See more information [here](./docs/nollup-hooks.md).
72 |
73 | ## Live Bindings
74 |
75 | For performance reasons, by default Nollup does not enable ESM live-bindings (but still supports circular dependencies). If you require live-bindings or are running into an issue where something imported is undefined, you can enable live-bindings. See more information [here](./docs/live-bindings.md).
76 |
77 | ## Caveats
78 |
79 | * Only Rollup configurations that make sense in development are implemented.
80 | * Might be some inconsistencies with Rollup, but should be fine for majority of projects.
81 | * May be issues with circular dependencies. See [here](./docs/circular.md) for more information.
82 |
--------------------------------------------------------------------------------
/docs/circular.md:
--------------------------------------------------------------------------------
1 | # Circular Dependencies
2 |
3 | While Nollup does its best to resolve circular dependencies at the moment, there may be situations which won't resolve correctly. To ensure circular dependencies work, ESM when parsing a module, scans that module for all export bindings, and will hoist all of the declarations, but will not run any code. Nollup tries it best to emulate this behaviour. Circular dependencies are better avoided for the best experience possible. See below on how to workaround this issue, and further information on how ESM handles circular dependencies.
4 |
5 | ## How to Workaround
6 |
7 | Usually this problem occurs with large third-party packages. Some packages, instead of bundling their code into a single module for use in ESM environments, they instead package their source code and point ```module``` inside their ```package.json``` to their source directory. However, these packages often include bundled versions of their code. You can redirect to these bundles by using a plugin such as ```@rollup/plugin-alias```, which will avoid the circular dependencies. Depending on the package, you may need to use an additional plugin such as ```@rollup/plugin-commonjs```.
8 |
9 | ```
10 | node_modules/
11 | my_library/
12 | package.json
13 | dist/
14 | my-library.cjs.js
15 | my-library.umd.js
16 | src/
17 | main.js
18 | somefile.js
19 | anotherfile.js
20 | ...
21 | ```
22 |
23 | ```
24 | {
25 | "name": "my-library",
26 | "main": "dist/my-library.cjs.js",
27 | "browser": "dist/my-library.umd.js",
28 | "module": "src/main.js"
29 | }
30 | ```
31 |
32 | ```
33 | alias({
34 | entries: [
35 | { find: 'my-library', replacement: require.resolve('my-library') }
36 | ]
37 | })
38 | ```
39 |
40 | Another option, especially if the library is rather large, is to not bundle the library at all, but instead to use a CDN or any other external storage separate from your app. Not only does it solve the problem of working around the circular dependency, but it may also significantly improve the bundling performance of your application:
41 |
42 | ```
43 |
44 |
45 | ```
46 |
47 | On a side note, packages that don't bundle their code for ESM environments are very inefficient. Because the files are separated, Nollup has to load and parse each file independently, which is costly in terms of compiling performance. I'd encourage all library authors to consider bundling their ESM code into an equivalent ```dist/my-library.esm.js``` file.
48 |
49 | ## How ESM Circular Works
50 |
51 | **Example 1**
52 |
53 | ```
54 | // A.js
55 | import B from './B';
56 |
57 | console.log('A');
58 | export function print (msg) {
59 | console.log(msg);
60 | }
61 |
62 | // B.js
63 | import { print } from './A';
64 |
65 | console.log('B');
66 | export default print('hello');
67 |
68 | // Output
69 | "B"
70 | "hello"
71 | "A"
72 | ```
73 |
74 | In this example, notice how B was able to use the ```print``` function, despite ```A.js``` not being executed, as you can see due to ```A``` being printed last. This is because ```print``` is a function declaration, and when ESM parses modules, function declarations are hoisted and exported immediately.
75 |
76 | **Example 2**
77 |
78 | ```
79 | // A.js
80 | import B from './B';
81 |
82 | console.log('A');
83 | export var print = function (msg) {
84 | console.log(msg);
85 | }
86 |
87 | // B.js
88 | import { print } from './A';
89 |
90 | console.log('B');
91 | export default print('hello');
92 |
93 | // Output
94 | "B"
95 | "Uncaught TypeError: print is not a function"
96 | ```
97 |
98 | For this example, although the ```print``` variable declaration will be hoisted, the implementation of the function will not occur until the module has been executed. Therefore, if ```B``` tries to use this function, it will be trying to execute ```undefined```.
99 |
100 | **Example 3**
101 |
102 | ```
103 | // A.js
104 | import B from './B';
105 |
106 | console.log('A');
107 | var prefix = '[INFO] ';
108 |
109 | function print (msg) {
110 | console.log(prefix + msg);
111 | }
112 |
113 | export { print };
114 |
115 | // B.js
116 | import { print } from './A';
117 |
118 | console.log('B');
119 | export default print('hello');
120 |
121 | // Output
122 | "B"
123 | "undefinedhello"
124 | "A"
125 | ```
126 |
127 | In this final example, although we didn't immediately export ```print```, because it was exported later in the module and is a function declaration, we can use it. However, notice that ```print``` points to a ```prefix``` variable. Because the module has not been executed, that value will be ```undefined```.
--------------------------------------------------------------------------------
/docs/cli.md:
--------------------------------------------------------------------------------
1 | # Nollup CLI
2 |
3 | The Nollup CLI is the preferred method of using Nollup. You're probably already using ```rollup -c``` in your ```package.json``` ```scripts``` section. Nollup functions the same way, you can use ```nollup -c``` to start a web server that reads your ```rollup.config.js``` file.
4 |
5 | ```
6 | "scripts": {
7 | "start": "nollup -c"
8 | }
9 | ```
10 |
11 | ## Flags
12 |
13 | The following flags can be passed into Nollup CLI. You can find a full description of each of these options [here](./options.md).
14 |
15 | * ```-c | --config [file]```
16 | * ```--rc [file]```
17 | * ```--content-base [folder]```
18 | * ```--history-api-fallback [fallback]?```
19 | * ```--hot```
20 | * ```--port [port]```
21 | * ```--verbose```
22 | * ```--hmr-host [host]```
23 | * ```--host [host]```
24 | * ```--public-path [folder]```
25 | * ```--environment [variables]```
26 | * ```--https```
27 | * ```--key [file]```
28 | * ```--cert [file]```
29 | * ```--live-bindings [mode]```
30 | * ```--configPlugin [plugin]```
31 |
32 | ## .nolluprc
33 |
34 | The CLI supports an external configuration using [.nolluprc](./nolluprc.md).
--------------------------------------------------------------------------------
/docs/compiler.md:
--------------------------------------------------------------------------------
1 | # Nollup Compiler API
2 |
3 | The compiler API is the lowest level available for working with Nollup. It is modelled after the Rollup API and can be swapped directly with each other. This API is intended for plugin authors to test their plugins in both Rollup and Nollup easily, and also for those who wish to create their own middleware.
4 |
5 | The compiler API does not start any web server, and does not provide HMR functionality. That's up the developer to implement.
6 |
7 | You can import the Nollup compiler using the following:
8 |
9 | ```
10 | let Nollup = require('nollup');
11 | ```
12 |
13 | Then you can use Nollup in the same way you would use Rollup's API:
14 |
15 | ```
16 | async function build () {
17 | // Prepare instance of nollup
18 | let bundle = await nollup(inputOptions);
19 |
20 | // Generate code
21 | let { output } = await bundle.generate(outputOptions);
22 |
23 | // Unlike Rollup, there's no write method.
24 | // Code should be served from memory.
25 | }
26 |
27 | build();
28 | ```
29 |
30 | There's no ```bundle.write()``` function because development servers should not be writing to disk.
31 |
32 | ## API
33 |
34 | ***Object* nollup(*Object* rollupConfig)**
35 |
36 | Receives a standard Rollup configuration with ```input``` and ```plugins```.
37 | It returns a bundle object from which you can call the methods below.
38 |
39 | ***Promise<Object>* bundle.generate(*Object* outputOptions)**
40 |
41 | The ```bundle.generate()``` function returns the following properties:
42 |
43 | * ```Object stats``` - Contains timing for bundle generation.
44 | * ```Array changes``` - Contained changed modules.
45 | * ```Array output``` - Same as Rollup, contains all generated files.
46 |
47 | ***void* bundle.invalidate(*String* filePath)**
48 |
49 | Invalidating marks the module that matches the provided filepath, so when ```generate()``` is called again, it will only compile that one module and rely on the cache for all other modules.
50 |
51 | ```watchChange``` plugin hook will also be triggered passing the module id.
52 |
53 | ***void* bundle.configure(*Object* options)**
54 |
55 | Configure Nollup compiler options. Pass an object with any of the below options:
56 |
57 | * ```String|Boolean liveBindings``` - Enable live-bindings in the compiled code. Supports ```true```, ```"with-scope"``` or ```"reference"```. See [Live Bindings](./live-bindings.md) for more information.
58 |
59 |
--------------------------------------------------------------------------------
/docs/dev-middleware.md:
--------------------------------------------------------------------------------
1 | # Nollup Dev Middleware
2 |
3 | The dev middleware is an ExpressJS middleware that can be plugged into an existing ExpressJS server. It is intended for situations where you want full control over the Express server and its configuration.
4 |
5 | The dev middleware can be imported by using the following:
6 |
7 | ```
8 | let NollupDevMiddleware = require('nollup/lib/dev-middleware');
9 | ```
10 |
11 | Once imported, you can plug the middleware into your existing ExpressJS server:
12 |
13 | ```
14 | app.use(NollupDevMiddleware(app, rollupConfig, {
15 | hot: true,
16 | contentBase: './public',
17 | ...
18 | }, server));
19 | ```
20 |
21 | ## Parameters
22 |
23 | * ```app``` - Reference to the Express app.
24 | * ```rollupConfig``` - Rollup configuration object.
25 | * ```nollupOptions``` - See below for options.
26 | * ```server``` - Optional reference to server if creating custom server instance.
27 |
28 | ## Options
29 |
30 | The following options can be passed into Nollup Dev Middleware. You can find a full description of each of these options [here](./options.md).
31 |
32 | * ```Boolean hot```
33 | * ```Boolean verbose```
34 | * ```String headers```
35 | * ```String hmrHost```
36 | * ```String contentBase```
37 | * ```String publicPath```
38 | * ```String|Boolean liveBindings```
39 |
--------------------------------------------------------------------------------
/docs/dev-server.md:
--------------------------------------------------------------------------------
1 | # Nollup Dev Server
2 |
3 | The dev server provides a function that when called, will start an ExpressJS web server and the Nollup compiler. It's intended to be used by developers who need to programmatically control when Nollup is started and run additional code around it.
4 |
5 | The dev server can be imported into your startup script using the following:
6 |
7 | ```
8 | let NollupDevServer = require('nollup/lib/dev-server');
9 | ```
10 |
11 | Once imported, you can call it anywhere you want, and it will start compiling and serving:
12 |
13 | ```
14 | NollupDevServer({
15 | hot: true,
16 | port: 9001,
17 | ...
18 | });
19 | ```
20 |
21 | ## Options
22 |
23 | The following options can be passed into Nollup Dev Server. You can find a full description of each of these options [here](./options.md).
24 |
25 | * ```Function before```
26 | * ```Function after```
27 | * ```Object|String config```
28 | * ```String rc```
29 | * ```Boolean hot```
30 | * ```Number port```
31 | * ```Boolean verbose```
32 | * ```String headers```
33 | * ```String hmrHost```
34 | * ```String host```
35 | * ```String contentBase```
36 | * ```String publicPath```
37 | * ```Object proxy```
38 | * ```Boolean|String historyApiFallback```
39 | * ```Boolean https```
40 | * ```String key```
41 | * ```String cert```
42 | * ```String|Boolean liveBindings```
43 | * ```String|String[] configPlugin```
44 |
45 | ## .nolluprc
46 |
47 | The dev server supports an external configuration using [.nolluprc](./nolluprc.md).
--------------------------------------------------------------------------------
/docs/hmr.md:
--------------------------------------------------------------------------------
1 | # Hot Module Replacement
2 |
3 | To generate development bundles, Nollup wraps each module into its own function scope. This allows Nollup to compile modules independently of the rest of the bundle, and allows updated code snippets to be sent over a websocket. While using the built-in dev middleware (server or CLI), Nollup will set up the websocket server for you, and will send the code changes to the browser. However, it is up to the developer to listen for those changes and react to them.
4 |
5 | Each module has access to ```require``` and ```module```. Many HMR frameworks usually assume these exist because they support Webpack or Parcel. Nollup follows this convention and implements them as well. Note, these are not to be used as replacements for ES ```import``` and ```export```. Their functionality is restricted to supporting HMR only.
6 |
7 | ## API
8 |
9 | * ```Object require(Number moduleId)``` - Import a module with the specified ID. This ID is auto-generated during bundling.
10 | * ```Object module``` - Contains information about the current module.
11 | * ```Number id``` - The ID of this module inside the bundle.
12 | * ```Array dependencies``` - The dependencies for this module.
13 | * ```Array dynamicDependencies``` - Dependencies imported with ```import()```.
14 | * ```Object exports``` - Contains named exports and default export.
15 | * ```Boolean invalidate``` - If module is to be reloaded when required, set this to true.
16 |
17 | ## Hot API
18 |
19 | When ```--hot``` is enabled, ```module.hot``` will be available. It provides the following properties:
20 |
21 | ***void* accept(*Function* callback)**
22 |
23 | Executes when the current module, or a dependency has been replaced.
24 | Passes ```e``` argument which an object containing information about the accept.
25 | The object contains the following:
26 |
27 | * ```disposed``` - Contains list of module ids disposed when bubbling to this accept handler.
28 |
29 | Note that in order for the module to be resolved, you must call ```require(module.id)``` inside the callback.
30 | This is slightly different from the way that other bundlers operates, which auto-requires before calling the accept handler.
31 | Due to backwards compatibility, this cannot be changed at the moment.
32 |
33 | However, if you call this function without passing a callback, it will auto-require the module.
34 |
35 | ***void* dispose(*Function* callback)**
36 |
37 | Executes when the module is about to be replaced.
38 | Callback receives ```data``` object. When the module is reloaded,
39 | this data can be read using ```module.hot.data```.
40 |
41 | ***String* status()**
42 |
43 | Provides the current status of HMR. It will be one of the following:
44 |
45 | * ```'idle'``` - Waiting for changes.
46 | * ```'check'``` - Bundler checking for updates.
47 | * ```'prepare'``` - Bundler getting ready to update.
48 | * ```'ready'``` - Bundler prepared updated.
49 | * ```'dispose'``` - ```dispose``` handler on module is being executed.
50 | * ```'apply'``` - ```accept``` handler on module is being executed.
51 |
52 | ***void* addStatusHandler(*Function* callback)**
53 |
54 | Executes when the status of HMR changes.
55 |
56 | ***void* removeStatusHandler(*Function* callback)**
57 |
58 | Removes the listener that matches the callback function.
59 |
60 | ## Adding Hot Support to App
61 |
62 | Out of the box, Nollup won't do anything to enable any hot functionality for your app.
63 | This has to be manually added by the developer using ```module.hot.accept``` callback.
64 | When a file is saved, Nollup will check the dependency tree for that file, and if any of its parents have defined a ```module.hot.accept``` callback, it will execute that callback. Developers can run whatever code they want in the callback to update their application.
65 |
66 | Usually there's two different approaches that are taken for the callback:
67 |
68 | ### Hot Reload
69 |
70 | When a file is saved, the browser will reload the page. Frameworks don't need to support this, and it can be added to any project easily.
71 |
72 | ```
73 | if (module) {
74 | module.hot.accept(() => {
75 | window.location.reload();
76 | });
77 | }
78 | ```
79 |
80 | ### Hot Module Replacement
81 |
82 | When a file is saved, only the changed module is replaced, the page is not refreshed. This is very powerful as it allows you to update your app while preserving as much state as possible. This has to be supported by the framework or plugin you are using. Plugins such as ```rollup-plugin-hot-css``` allow you to update your CSS without refreshing the page. Please refer to the framework's documentation on how to add HMR support to your app.
83 |
84 | You can also use a combination of HMR with Hot Reload. For example you can use the CSS plugin, but use a fallback accept callback that will refresh the page instead as described above.
85 |
86 | ### Additional Build Configuration for HMR
87 |
88 | In your build configuration, if your code includes ```module```, it may be necessary to explicitly inform Rollup to remove all references to ```module```, otherwise your application may break when compiled with Rollup. This can be done using a plugin such as ```rollup-plugin-terser```. If your HMR is provided by a Rollup plugin, this probably isn't necessary.
89 |
90 | ```
91 | terser({
92 | compress: {
93 | global_defs: {
94 | module: false
95 | }
96 | }
97 | });
98 | ```
--------------------------------------------------------------------------------
/docs/live-bindings.md:
--------------------------------------------------------------------------------
1 | # Live Bindings
2 |
3 | ES Modules provides a feature called live bindings. When you import from a module, you're not importing a copy or a reference to that export, you're importing a "binding". If the exporter changes the value of an export, that change will be reflected in the imported version. This feature primarily exists as a means to solve circular dependencies with ES modules.
4 |
5 | **counter.js**
6 | ```
7 | export var count = 0;
8 |
9 | export function increment () {
10 | count++;
11 | }
12 | ```
13 |
14 | **main.js**
15 | ```
16 | import { count, increment } from './counter.js';
17 |
18 | console.log(count); // 0
19 | increment();
20 | console.log(count); // 1
21 | ```
22 |
23 | Unfortunately, when bundling modules together, we lose this ability and have to simulate it. Rollup simulates this feature by using Scope Hoisting. In other words, Rollup combines the modules together into a single module, so all references to an export are pointing to the same declaration, therefore simulating live-bindings.
24 |
25 | Scope Hoisting is an expensive process, and typically only done during production compilation. Nollup like other development bundlers, wraps each module into its own function scope, and concatenates those function scopes together into a single bundle. This is very fast to do, but it means that live-bindings are gone, so another way of simulating them needs to be achieved.
26 |
27 | By default, for performance reasons, Nollup doesn't enable live-bindings. It's a rarely used feature, and circular dependencies can be solved without the need for live-bindings. However, there may be times when you do need it. To help with those cases, Nollup provides the ```liveBindings``` flag.
28 |
29 | **.nolluprc.js**
30 | ```
31 | liveBindings: true | "with-scope" | "reference"
32 | ```
33 |
34 | **CLI**
35 |
36 | ```
37 | nollup -c --live-bindings
38 | ```
39 |
40 | ## Reference
41 |
42 | This is the default mode if using ```liveBindings: true```, or if set to ```reference```.
43 |
44 | This mode will traverse the abstract syntax tree for each file, scanning for usages of an import. This is more complicated than it sounds, because there may be other variables that conflict with the import, and the import identifier may be used in different contexts that don't actually reference the import.
45 |
46 | Below is how Nollup will convert live-bindings in this mode:
47 |
48 | ```
49 | // inside module
50 | var count = 0; __e__('count', () => count);
51 |
52 | // What the export is doing
53 | Object.defineProperty(module.exports, 'count', {
54 | get: () => count,
55 | enumerable: true
56 | });
57 | ```
58 |
59 | ```
60 | // defined outside module code
61 | var __i__ = {};
62 | Object.defineProperty(__i__, 'count', {
63 | get() { return _i0().count; }
64 | });
65 |
66 | // inside module code
67 | console.log(__i__.count); // 0
68 | __i__.increment();
69 | console.log(__i__.count); // 1
70 | ```
71 |
72 | **Pros:**
73 |
74 | * Closest to simulating ES modules live-bindings.
75 | * Native dynamic import() can be used to import chunks.
76 | * Supports bundles that export something at the very end.
77 | * Can be used with type="module" script tags.
78 |
79 | **Cons:**
80 |
81 | * Breaks debugging symbols. Hovering over "count" in the source map won't show anything.
82 | * It can be costly to scan the AST for usages of the bindings.
83 | * May not be compatible with latest JavaScript syntax.
84 |
85 | ## With Scope
86 |
87 | This is enabled by setting ```liveBindings: "with-scope"```.
88 |
89 | This option is very much experimental, but it's another solution to implementing live-bindings. If you're not familiar with ```with``` blocks, it's a very old feature of JavaScript that's not used for very good reason, it creates confusing code. ```with``` blocks make all properties on an object accessible as if they were normal variables. This can lead to some very ambiguous situations.
90 |
91 | ```
92 | function f(x, o) {
93 | with (o) {
94 | console.log(x); // is this o.x, or the x param?
95 | }
96 | }
97 | ```
98 |
99 | However, it can be possible to use this to simulate live-bindings. Below is how Nollup converts live-bindings using ```with``` scope:
100 |
101 | ```
102 | // inside module
103 | var count = 0; __e__('count', () => count);
104 |
105 | // What the export is doing
106 | Object.defineProperty(module.exports, 'count', {
107 | get: () => count,
108 | enumerable: true
109 | });
110 | ```
111 |
112 | ```
113 | // defined outside module code
114 | var __i__ = {};
115 | Object.defineProperty(__i__, 'count', {
116 | get() { return _i0().count; }
117 | });
118 |
119 | // inside module code
120 | with (__i__) {
121 | console.log(count);
122 | increment();
123 | console.log(count);
124 | }
125 | ```
126 |
127 | **Pros:**
128 |
129 | * Very fast, no need to traverse the AST and do conversions of symbols.
130 | * Keeps debugging symbols intact.
131 | * Will always work with latest JavaScript standards.
132 |
133 | **Cons:**
134 |
135 | * Cannot be used in strict mode.
136 | * Needs to use fetch and eval for dynamic import() as that will try to evaluate as strict.
137 | * May incur runtime performance penalty as each variable used has to be checked as an object property.
138 | * Cannot have the bundle export anything.
139 | * Cannot use type="module" when importing the bundle.
140 | * Cannot have external imports.
141 |
--------------------------------------------------------------------------------
/docs/nollup-hooks.md:
--------------------------------------------------------------------------------
1 | # Nollup Plugin Hooks
2 |
3 | Nollup provides additional plugin hooks for plugins. This allows features like HMR to be implemented.
4 |
5 | ***String* nollupBundleInit()**
6 |
7 | Injected into the bundle before the first module in the bundle is required.
8 | It has access to ```instances``` and ```modules```.
9 |
10 | ```instances``` is an array of instantiated modules. Each module has the following properties:
11 |
12 | * ```Number id``` - The ID of the module that it was instantiated from.
13 | * ```Object exports``` - Export code from the module.
14 | * ```Array dependencies``` - Module IDs this module depends on.
15 | * ```Array dynamicDependencies``` - Dependencies imported by ```import()```.
16 | * ```Boolean invalidate``` - If set to true, the module will be invalidated and executed again when required.
17 |
18 | ```modules``` is an object of module IDs with their code.
19 |
20 | ***String* nollupModuleInit()**
21 |
22 | Injected into the bundle before a module is instantiated.
23 | It has access to ```instances```, ```modules``` and ```module``` which is the module being instantiated.
24 |
25 | ***String* nollupModuleWrap()**
26 |
27 | Wrap a module instantiation code with additional code.
28 | Useful for libraries providing Hot Module Replacement and need to add commonly functionality to all modules.
29 | It has access to ```instances```, ```modules``` and ```module``` which is the module being wrapped.
--------------------------------------------------------------------------------
/docs/nolluprc.md:
--------------------------------------------------------------------------------
1 | ## .nolluprc
2 |
3 | Configuration file that can be used to pass configuration instead of as flags through the CLI.
4 |
5 | ```
6 | {
7 | "hot": true,
8 | "contentBase": "./public"
9 | }
10 | ```
11 |
12 | A JavaScript file called ```.nolluprc.js``` or specified file can be used instead.
13 |
14 | ```
15 | module.exports = {
16 | hot: true,
17 | contentBase: './public'
18 | };
19 | ```
20 |
21 | See "Nollup Options" for list of available options.
--------------------------------------------------------------------------------
/docs/options.md:
--------------------------------------------------------------------------------
1 | # Nollup Options
2 |
3 | This list provides a description of all of the options for the [CLI](./cli.md), [dev server](./dev-server.md) and [dev middleware](./dev-middleware.md). See their respective pages for the exact options each of them support.
4 |
5 | | Type | Name | Description |
6 | |------|------|-------------|
7 | | ```String\|Object``` | ```config``` | Pass a Rollup configuration file. By default it will look for ```rollup.config.js``` but can be specified otherwise. If object is supported, can receive Rollup config object. |
8 | | ```String``` | ```rc``` | Pass a Nollup configuration file. By default it will look for one of ```.nolluprc```, ```.nolluprc.js```. |
9 | | ```String``` | ```contentBase``` | Folder to serve static content from. Typically the content would contain additional resources like images. By default it will be looking in ```./```. |
10 | | ```Boolean``` | ```hot``` | Enable Hot Module Replacement. Default is ```false```. |
11 | | ```Number``` | ```port``` | Port number to run server on. Default is ```8080```. |
12 | | ```Boolean\|String``` | ```historyApiFallback``` | If set to true, it will fallback to ```index.html``` if accessing a file that doesn't exist. You can pass a string to fallback to a different file. Default is ```false```. |
13 | | ```String``` | ```publicPath``` | All generated files will be served from this URL. Default is ```/``` |
14 | | ```String``` | ```environment``` | Pass environment variables that are set to ```process.ENV```. |
15 | | ```Object``` | ```proxy``` | Object keys are paths to match. Value can be the domain to proxy to. ```"api": "http://localhost:8080"``` will have a request such as ```/api/todos``` proxy to ```http://localhost:8080/api/todos```. In addition the value can be an object with host key for domain and any additional configurations that [express-http-proxy](https://github.com/villadora/express-http-proxy) consumes. ```"api": {host: 'http://localhost:8080", changeOrigin: true}``` will have a request such as ```/api/todos``` proxy to ```http://localhost:8080/api/todos``` with changeOrigin flag set to true. |
16 | | ```Boolean``` | ```verbose``` | Enable verbose logging. Default is ```false```. |
17 | | ```Object``` | ```headers``` | Provide custom headers for Express server responses. Useful to set cors headers for the server. |
18 | | ```String``` | ```hmrHost``` | Host to connect to for HMR. Default is ```window.location.host```. Useful for Electron environments. |
19 | | ```String``` | ```host``` | Specify the host to use. Default is ```localhost```. Useful for allowing remote connections, eg. ```0.0.0.0```|
20 | | ```Function``` | ```before``` | Receives Express app as argument. You can inject custom middleware before Nollup dev middleware. |
21 | | ```Function``` | ```after``` | Receives Express app as argument. You can inject custom middleware after Nollup dev middleware. |
22 | | ```Boolean``` | ```https``` | Enable https. Default is ```false```. Requires ```key``` and ```cert``` to be set |
23 | | ```String``` | ```key``` | Path to the private key file to use with https. |
24 | | ```String``` | ```cert``` | Path to the certificate file to use with https. |
25 | | ```String\|Boolean``` | ```liveBindings``` | Enable live-bindings. Default is ```false```. Supports ```"with-scope"``` or ```"reference"```. If set to ```true```, it will use ```"reference"```. See [Live Bindings](./live-bindings.md) for more information. |
26 | | ```String\|String[]``` | ```configPlugin``` | Parse the config file using the provide plugin. Can pass array of plugins as well. |
27 |
28 |
--------------------------------------------------------------------------------
/docs/plugins.md:
--------------------------------------------------------------------------------
1 | # Nollup Plugins
2 |
3 | The following Rollup plugins are implemented to be used with Nollup:
4 |
5 | * [rollup-plugin-hot-css](https://github.com/PepsRyuu/rollup-plugin-hot-css) - Load CSS files with HMR support.
6 | * [rollup-plugin-react-refresh](https://github.com/PepsRyuu/rollup-plugin-react-refresh) - Nollup plugin for HMR in React apps.
7 | * [rollup-plugin-commonjs-alternate](https://github.com/PepsRyuu/rollup-plugin-commonjs-alternate) - CommonJS loader that supports React Hot Loader.
8 | * [@prefresh/nollup](https://github.com/JoviDeCroock/prefresh) - HMR for Preact apps.
9 |
--------------------------------------------------------------------------------
/docs/rollup-config.md:
--------------------------------------------------------------------------------
1 | # Supported Rollup Config Options
2 |
3 | Nollup supports a subset of Rollup configuration options. The reason for this is because only a subset of the options make sense to be used in development, while others make more sense of production and optimising a production build. For options that Nollup doesn't understand, they are just simply ignored. Below describes what is supported.
4 |
5 | See [Rollup](https://rollupjs.org/guide/en/) documentation for more information about each of these options.
6 |
7 | ## Options
8 |
9 | * ```input``` - Supports ```string```, ```Object``` or ```Array```.
10 | * ```output``` - See below for support options.
11 | * ```plugins``` - Use the same Rollup plugins.
12 | * ```external``` - Supports ```Array``` or ```Function```.
13 | * ```acornInjectPlugins``` - Can pass array of additional Acorn plugins here.
14 | * ```watch``` See below for notes on this option.
15 | * ```context``` - Same as Rollup.
16 | * ```moduleContext``` - Same as rollup.
17 |
18 | ## Output Options
19 |
20 | * ```file``` - If using this option, the full path is the URL.
21 | * ```dir``` - Nothing is done with it. Point it to ```dist``` or similar.
22 | * ```entryFileNames``` - See below note.
23 | * ```chunkFileNames``` - See below note.
24 | * ```assetFileNames``` - See below note.
25 | * ```format``` - Only support for ```es```, ```cjs```, ```amd``` or ```iife```.
26 | * ```globals``` - Remapping for window variables.
27 |
28 | For file name pattern options, when the bundle is generated, it will serve files based on what the pattern says. The ```dir``` option is completely ignored and not part of the generated URL.
29 |
30 | Important to note as well that ```[hash]``` is never converted. This is intentional to make it easier to reference files during development, especially in files such as ```index.html```. For production builds with Rollup, it's recommended to use plugins such as [rollup-plugin-static-files](https://github.com/PepsRyuu/rollup-plugin-static-files) to auto-inject the hash.
31 |
32 | ## Watch Options
33 |
34 | Nollup does not provide a ```watch()``` function like Rollup does, instead providing a web server. For compatibility, the Nollup dev server and middleware will respect this option. Both of the following options are supported:
35 |
36 | * ```include``` - Will only listen to these directories for changes.
37 | * ```exclude``` - Will listen to all directories but these ones for changes.
38 |
39 | Nollup also injects the ```process.env.ROLLUP_WATCH``` environment variable. To differentiate between Rollup watch and Nollup though, there's also the ```process.env.NOLLUP``` environment variable.
40 |
41 | ## Plugin Hooks
42 |
43 | [Rollup plugins](https://rollupjs.org/guide/en#plugins-overview) should work.
44 |
45 | The following lifecycle methods have been implemented:
46 |
47 | ```
48 | buildStart,
49 | buildEnd,
50 | options,
51 | outputOptions,
52 | intro,
53 | outro,
54 | banner,
55 | footer,
56 | generateBundle,
57 | resolveDynamicImport,
58 | resolveId,
59 | load,
60 | transform,
61 | renderChunk,
62 | renderError,
63 | renderStart,
64 | resolveFileUrl,
65 | resolveImportMeta,
66 | moduleParsed
67 | ```
68 |
69 | ### Plugin Context
70 |
71 | See [Rollup Plugin Context](https://rollupjs.org/guide/en#context) for more information.
72 | Plugins can use the following methods in their lifecycle methods.
73 |
74 | ```
75 | this.meta
76 | this.addWatchFile(filepath)
77 | this.emitFile(file)
78 | this.getFileName(id)
79 | this.parse(code, acornOptions)
80 | this.warn(warning)
81 | this.error(error)
82 | this.emitAsset(assetName, source)
83 | this.getAssetFileName(assetId)
84 | this.emitChunk(id, options)
85 | this.getChunkFileName(chunkId)
86 | this.setAssetSource(assetId, source);
87 | this.resolveId(importee, importer)
88 | this.getCombinedSourcemap()
89 | this.getModuleInfo(moduleId)
90 | this.moduleIds
91 | this.resolve(importee, importer, opts)
92 | ```
--------------------------------------------------------------------------------
/docs/sdk.md:
--------------------------------------------------------------------------------
1 | # Nollup SDK
2 |
3 | ## **Highly experimental, use at risk**
4 |
5 | With more tooling using a similar approach of reusing Rollup plugins, there has been multiple implementations of the Rollup plugin engine implemented. To help some of these tools, Nollup has been refactored to decouple the plugin engine from the rest of the tool, to allow for reuse and consistency in plugin usage.
6 |
7 | This is designed for other bundlers (and no-bundlers) to use, it is not intended for app developers to use at all.
8 |
9 | As this is experimental, **there's no backwards compatibility guaranteed** and this may even be removed in the future. This is highly subject to change depending on feedback.
10 |
11 | ## APIs
12 |
13 | **RollupConfigContainer**
14 |
15 | This class manages the Rollup config. It will do all of the necessary defaults, normalizing and formatting, as well as calling the ```options``` and ```outputOptions``` hooks.
16 |
17 | ```
18 | import { RollupConfigContainer } from 'nollup/lib/sdk';
19 |
20 | // Handles input, plugins, and external options
21 | let config = new RollupConfigContainer(rollupOptions);
22 |
23 | // Handles the output option
24 | config.setOutputOptions(rollupOutputOptions);
25 | ```
26 |
27 | **PluginContainer**
28 |
29 | This class manages the use of the Rollup plugins. It provides the ability to call plugin hooks, and provides plugin hooks with a context. This class doesn't fully implement each context function as some are implementation specific depending on the type of bundler you're creating, so there are callbacks instead which can be implemented.
30 |
31 | ```
32 | import { PluginContainer } from 'nollup/lib/sdk';
33 |
34 | let container = new PluginContainer(rollupConfigContainer, acornParser);
35 |
36 | // Call start before calling any hook.
37 | // This is important for errors to trigger correctly.
38 | // If an error is thrown, this needs to be called again.
39 | container.start();
40 |
41 | // Call hooks as you need them
42 | container.hooks.buildStart(options);
43 | container.hooks.resolveDynamicImport(id, parentId);
44 | container.hooks.resolveId(id, parentId, options);
45 | container.hooks.load(filepath, parentFilepath);
46 | container.hooks.transform(code, id, map);
47 | container.hooks.watchChange(id);
48 | container.hooks.buildEnd(error);
49 | container.hooks.renderStart(outputOptions, inputOptions);
50 | container.hooks.banner();
51 | container.hooks.footer();
52 | container.hooks.intro();
53 | container.hooks.outro();
54 | container.hooks.resolveFileUrl(metaProperty, referenceId, fileName, chunkId, moduleId);
55 | container.hooks.resolveImportMeta(metaProperty, chunkId, moduleId);
56 | container.hooks.renderChunk(code, chunkInfo, outputOptions);
57 | container.hooks.renderError(error);
58 | container.hooks.generateBundle(outputOptions, bundle);
59 | container.hooks.moduleParsed(id);
60 |
61 | // Implement the callbacks for full functionality
62 | container.onAddWatchFile((id, parentId) => {
63 | // Track your files to watch
64 | });
65 |
66 | container.onGetWatchFiles(() => {
67 | // return a list of files being watched
68 | });
69 |
70 | container.onEmitFile((referenceId, emitted) => {
71 | // Do something with the emitted file
72 | });
73 |
74 | container.onGetFileName(referenceId => {
75 | // return the output file name for the provided id
76 | });
77 |
78 | container.onSetAssetSource((referenceId, source) => {
79 | // Override the source of the referenced asset
80 | });
81 |
82 | container.onGetModuleIds(() => {
83 | // return an iterable list of unique module ids in this bundle
84 | });
85 |
86 | container.onGetModuleInfo(id => {
87 | // return object with the module info based on the id
88 | })
89 | ```
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | ## Getting Started
4 |
5 | * ```npm install``` to install all of the dependencies.
6 | * ```npm start``` will run the Nollup dev server.
7 | * ```npm run build``` will use Rollup to generate a production bundle.
8 |
9 | Make sure you have ran ```npm install``` in the root of the Nollup repository. Every example uses Nollup relatively with ```node ../../lib/cli.js```. In a normal project, this would be ```nollup```.
10 |
11 | ## example-preact
12 |
13 | * Uses Preact as the base framework.
14 | * Supports HMR using ```prefresh```.
15 | * Supports HMR for CSS styles.
16 | * Config shows how to use different plugins for dev and prod.
17 | * Note that plugins never write to disk during dev.
18 |
19 | ## example-react-refresh
20 |
21 | * Uses React as the base framework.
22 | * Supports HMR using ```react-refresh```.
23 | * Supports HMR for CSS styles.
24 | * Uses CommonJS plugins that supports HMR by not parsing out dynamic ```require``` calls.
25 | * The alternative CJS plugin also allows for ```require``` inside ES modules.
26 | * Uses replace plugin because of ```process.env.NODE_ENV``` not existing in browsers.
27 |
28 | ## example-react-hot-loader
29 |
30 | * Uses React as the base framework.
31 | * Supports HMR using the old ```react-hot-loader```.
32 | * Supports HMR for CSS styles.
33 | * See above about CJS plugin.
34 |
35 | ## example-dynamic-import
36 |
37 | * Uses React as the base framework.
38 | * Uses dynamic import to generate separate file.
39 | * Demonstrates HMR working across multiple generated JavaScript chunks.
40 |
41 | ## example-multi-bundle
42 |
43 | * Demonstrates Nollup CLI supporting multiple bundles to be generated.
44 | * Both bundles are able to use HMR.
45 |
46 | ## example-emit-chunk
47 |
48 | * Demonstrates the ```emitFile``` API to emit a custom chunk.
49 | * Custom chunk is created using a custom plugin to emit web workers as separate files.
50 |
51 | ## example-globals
52 |
53 | * Demonstrates ```external``` option to access global variables in ```iife``` format.
54 |
55 | ## example-circular
56 |
57 | * Demonstrates big libraries with circular dependencies (```moment```) working in Nollup.
58 |
59 | ## example-public-path
60 |
61 | * Demonstrates how to use ```publicPath``` for enabling single page applications to work in a subdirectory.
62 |
63 | ## example-single-file-bundle
64 |
65 | * Although not recommended, Nollup works with the old Rollup style projects that emit to the ```public``` directory.
--------------------------------------------------------------------------------
/examples/example-amd/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "shx rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --hot --content-base public --port 9001",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c"
6 | },
7 | "dependencies": {
8 | "requirejs": "^2.3.6"
9 | },
10 | "devDependencies": {
11 | "@babel/core": "^7.13.14",
12 | "@rollup/plugin-babel": "^5.3.0",
13 | "@rollup/plugin-node-resolve": "^11.2.1",
14 | "cross-env": "^7.0.3",
15 | "rollup": "^2.44.0",
16 | "rollup-plugin-hot-css": "^0.2.1",
17 | "rollup-plugin-static-files": "^0.2.0",
18 | "shx": "^0.3.2"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/example-amd/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | AMD Format
7 |
8 |
9 |
10 |
11 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/example-amd/public/my-external-module-dynamic.js:
--------------------------------------------------------------------------------
1 | define(function () {
2 | return {
3 | message: 'my-external-module-dynamic'
4 | }
5 | });
--------------------------------------------------------------------------------
/examples/example-amd/public/my-external-module.js:
--------------------------------------------------------------------------------
1 | define(function () {
2 | return {
3 | message: 'my-external-module'
4 | }
5 | });
--------------------------------------------------------------------------------
/examples/example-amd/rollup.config.js:
--------------------------------------------------------------------------------
1 | import node_resolve from '@rollup/plugin-node-resolve';
2 | import babel from '@rollup/plugin-babel';
3 | import static_files from 'rollup-plugin-static-files';
4 |
5 | let config = {
6 | input: ['./src/single-export-main.js', './src/multiple-export-main.js', './src/dynamic-import-main.js'],
7 | external: ['my-external-module', 'my-external-module-dynamic'],
8 | output: {
9 | dir: 'dist',
10 | format: 'amd',
11 | entryFileNames: '[name].js',
12 | assetFileNames: '[name][extname]'
13 | },
14 | plugins: [
15 | {
16 | generateBundle() {
17 | let fs = require('fs');
18 |
19 | this.emitFile({
20 | type: 'asset',
21 | fileName: 'require.js',
22 | source: fs.readFileSync('node_modules/requirejs/require.js')
23 | });
24 | }
25 | },
26 | babel({
27 | exclude: 'node_modules/**'
28 | }),
29 | node_resolve()
30 | ]
31 | }
32 |
33 | if (process.env.NODE_ENV === 'production') {
34 | config.plugins = config.plugins.concat([
35 | static_files({
36 | include: ['./public']
37 | })
38 | ]);
39 | }
40 |
41 | export default config;
42 |
--------------------------------------------------------------------------------
/examples/example-amd/src/dynamic-import-main.js:
--------------------------------------------------------------------------------
1 | import('./my-dynamic-module').then(mod => {
2 | let el = document.createElement('div');
3 | el.textContent = mod.default;
4 | document.body.appendChild(el);
5 | });
--------------------------------------------------------------------------------
/examples/example-amd/src/multiple-export-main.js:
--------------------------------------------------------------------------------
1 | export default 'multiple-export-default';
2 | export var named = 'multiple-export-named';
--------------------------------------------------------------------------------
/examples/example-amd/src/my-dynamic-module.js:
--------------------------------------------------------------------------------
1 | import MyExternalModuleDynamic from 'my-external-module-dynamic';
2 |
3 | export default MyExternalModuleDynamic.message;
--------------------------------------------------------------------------------
/examples/example-amd/src/single-export-main.js:
--------------------------------------------------------------------------------
1 | import { message } from 'my-external-module';
2 |
3 | export default message;
--------------------------------------------------------------------------------
/examples/example-circular/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "rm -rf target && rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --content-base public --port 9001 --hot",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c"
6 | },
7 | "devDependencies": {
8 | "rollup-plugin-node-resolve": "^4.0.1",
9 | "cross-env": "^7.0.3",
10 | "rollup": "^2.44.0"
11 | },
12 | "dependencies": {
13 | "moment": "^2.24.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/example-circular/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Circular Deps
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/example-circular/rollup.config.js:
--------------------------------------------------------------------------------
1 | import node_resolve from 'rollup-plugin-node-resolve';
2 |
3 | export default {
4 | input: './src/main.js',
5 | output: {
6 | dir: 'dist',
7 | format: 'esm',
8 | entryFileNames: '[name].js'
9 | },
10 | plugins: [
11 | node_resolve({
12 | jsnext: true
13 | })
14 | ]
15 | }
--------------------------------------------------------------------------------
/examples/example-circular/src/A1.js:
--------------------------------------------------------------------------------
1 | import A2 from './A2.js';
2 | import B from './B.js';
3 |
4 | export function create_wrapper_print (message) {
5 | return () => console.log('wrapper-a1', message);
6 | }
7 |
8 | B();
9 |
10 | export default () => {
11 | A2();
12 | }
--------------------------------------------------------------------------------
/examples/example-circular/src/A2.js:
--------------------------------------------------------------------------------
1 | import A3 from './A3.js';
2 |
3 | export default () => {
4 | console.log('A2');
5 | A3();
6 | }
--------------------------------------------------------------------------------
/examples/example-circular/src/A3.js:
--------------------------------------------------------------------------------
1 | import { create_wrapper_print } from './A1.js';
2 |
3 | export default create_wrapper_print('A3');
--------------------------------------------------------------------------------
/examples/example-circular/src/B.js:
--------------------------------------------------------------------------------
1 | export default function () {
2 | console.log('B');
3 | }
--------------------------------------------------------------------------------
/examples/example-circular/src/main.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 | import A1 from './A1.js';
3 |
4 | A1();
5 |
6 | document.body.innerHTML = `
7 |
8 | Console should say the following:
9 |
10 | B
11 | A2
12 | wrapper-a1 A3
13 |
14 |
15 |
16 | Date from MomentJS: ${moment(new Date()).format('YYYY-MM-DD hh:mm')}
17 |
18 | `;
--------------------------------------------------------------------------------
/examples/example-dynamic-import/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-react"],
3 | "plugins": ["react-hot-loader/babel", "@babel/plugin-syntax-dynamic-import"]
4 | }
--------------------------------------------------------------------------------
/examples/example-dynamic-import/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "shx rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --hot --content-base public --port 9001",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c"
6 | },
7 | "dependencies": {
8 | "react": "^17.0.2",
9 | "react-dom": "^17.0.2"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.13.14",
13 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
14 | "@babel/preset-react": "^7.13.13",
15 | "@rollup/plugin-babel": "^5.3.0",
16 | "@rollup/plugin-node-resolve": "^11.2.1",
17 | "cross-env": "^7.0.3",
18 | "react-hot-loader": "^4.12.11",
19 | "rollup": "^2.44.0",
20 | "rollup-plugin-commonjs-alternate": "^0.8.0",
21 | "rollup-plugin-hot-css": "^0.4.0",
22 | "rollup-plugin-static-files": "^0.2.0",
23 | "rollup-plugin-terser": "^7.0.2",
24 | "shx": "^0.3.2"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/example-dynamic-import/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | HMR Test
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/example-dynamic-import/rollup.config.js:
--------------------------------------------------------------------------------
1 | import node_resolve from '@rollup/plugin-node-resolve';
2 | import babel from '@rollup/plugin-babel';
3 | import hotcss from 'rollup-plugin-hot-css';
4 | import commonjs from 'rollup-plugin-commonjs-alternate';
5 | import static_files from 'rollup-plugin-static-files';
6 | import { terser } from 'rollup-plugin-terser';
7 |
8 | let config = {
9 | input: './src/main.js',
10 | output: {
11 | dir: 'dist',
12 | format: 'esm',
13 | entryFileNames: '[name].[hash].js',
14 | assetFileNames: '[name].[hash][extname]'
15 | },
16 | plugins: [
17 | hotcss({
18 | hot: process.env.NODE_ENV === 'development',
19 | filename: 'styles.css'
20 | }),
21 | babel({
22 | exclude: 'node_modules/**'
23 | }),
24 | node_resolve(),
25 | commonjs({
26 | define: {
27 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
28 | }
29 | })
30 | ]
31 | }
32 |
33 | if (process.env.NODE_ENV === 'production') {
34 | config.plugins = config.plugins.concat([
35 | static_files({
36 | include: ['./public']
37 | }),
38 | terser({
39 | compress: {
40 | global_defs: {
41 | module: false
42 | }
43 | }
44 | })
45 | ]);
46 | }
47 |
48 | export default config;
--------------------------------------------------------------------------------
/examples/example-dynamic-import/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: red;
3 | }
--------------------------------------------------------------------------------
/examples/example-dynamic-import/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './App.css';
3 |
4 | class App extends Component {
5 |
6 | constructor () {
7 | super();
8 |
9 | this.activeComponent = undefined;
10 | }
11 |
12 | componentDidMount () {
13 | import('./Counter').then(component => {
14 | this.activeComponent = component.default;
15 | this.forceUpdate();
16 | });
17 | }
18 |
19 | render () {
20 | let ActiveComponent = this.activeComponent;
21 |
22 | return (
23 |
24 |
Hello World
25 | {this.activeComponent &&
}
26 |
27 | );
28 |
29 | }
30 | }
31 |
32 | if (process.env.NODE_ENV === 'development') {
33 | App = require('react-hot-loader').hot(module)(App);
34 | }
35 |
36 | export default App;
--------------------------------------------------------------------------------
/examples/example-dynamic-import/src/Counter.css:
--------------------------------------------------------------------------------
1 | .Counter {
2 | font-size: 40px;
3 | color: green;
4 | }
--------------------------------------------------------------------------------
/examples/example-dynamic-import/src/Counter.js:
--------------------------------------------------------------------------------
1 | import React , { Component } from 'react';
2 | import './Counter.css';
3 |
4 | class Counter extends Component {
5 | constructor () {
6 | super();
7 |
8 | this.state = {
9 | count: 0
10 | };
11 | }
12 |
13 | componentDidMount() {
14 | this.interval = setInterval(() => {
15 | this.setState({
16 | count: this.state.count + 1
17 | })
18 | }, 200);
19 | }
20 |
21 | componentWillUnmount() {
22 | clearInterval(this.interval)
23 | }
24 |
25 | render() {
26 | return Counter: {this.state.count}
27 | }
28 | }
29 |
30 | if (process.env.NODE_ENV === 'development') {
31 | Counter = require('react-hot-loader').hot(module)(Counter);
32 | }
33 |
34 | export default Counter;
--------------------------------------------------------------------------------
/examples/example-dynamic-import/src/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | let root = document.querySelector('#app');
6 | document.body.appendChild(root);
7 | ReactDOM.render( , root);
--------------------------------------------------------------------------------
/examples/example-emit-chunk/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "rm -rf target && rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --content-base public --port 9001 --hot",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c"
6 | },
7 | "devDependencies": {
8 | "cross-env": "^7.0.3",
9 | "rollup": "^2.44.0",
10 | "rollup-plugin-static-files": "^0.2.0"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/example-emit-chunk/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Emit Chunk
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/example-emit-chunk/rollup.config.js:
--------------------------------------------------------------------------------
1 | import static_files from 'rollup-plugin-static-files';
2 |
3 | const WEB_WORKER_PREFIX = 'web-worker:';
4 |
5 | function WebWorkerPlugin () {
6 | return {
7 | load (id) {
8 | if (id.startsWith(WEB_WORKER_PREFIX)) {
9 | let cid = this.emitFile({
10 | type: 'chunk',
11 | id: id.slice(WEB_WORKER_PREFIX.length)
12 | });
13 |
14 | return `export default function () {
15 | return new Worker(import.meta.ROLLUP_FILE_URL_${cid});
16 | }`;
17 | }
18 | },
19 |
20 | resolveId (source, importer) {
21 | if (source.startsWith(WEB_WORKER_PREFIX)) {
22 | return this.resolve(source.slice(WEB_WORKER_PREFIX.length), importer).then(
23 | resolvedId => WEB_WORKER_PREFIX + resolvedId.id
24 | );
25 | }
26 | return null;
27 | }
28 | };
29 | }
30 |
31 | export default {
32 | input: 'src/main.js',
33 | output: {
34 | format: 'esm',
35 | dir: 'dist'
36 | },
37 | plugins: [
38 | WebWorkerPlugin(),
39 | process.env.NODE_ENV === 'production' && static_files({
40 | include: ['./public']
41 | })
42 | ]
43 | }
--------------------------------------------------------------------------------
/examples/example-emit-chunk/src/main.js:
--------------------------------------------------------------------------------
1 | import WorkerColor from 'web-worker:./worker-color';
2 | import WorkerSize from 'web-worker:./worker-size';
3 |
4 | let workerColor = new WorkerColor();
5 | let workerSize = new WorkerSize();
6 |
7 | workerColor.onmessage = function (e) {
8 | document.body.style.color = e.data.color;
9 | }
10 |
11 | workerSize.onmessage = function (e) {
12 | document.body.style.fontSize = e.data.size + 'px';
13 | }
14 |
15 | document.body.innerHTML = 'Hello World ';
--------------------------------------------------------------------------------
/examples/example-emit-chunk/src/shared.js:
--------------------------------------------------------------------------------
1 | export const color = 'green';
2 | export const size = 30;
--------------------------------------------------------------------------------
/examples/example-emit-chunk/src/worker-color.js:
--------------------------------------------------------------------------------
1 | import { color } from './shared';
2 |
3 | if (self.interval) {
4 | clearInterval(self.interval);
5 | }
6 |
7 | self.interval = setInterval(() => {
8 | self.postMessage({
9 | color
10 | });
11 | }, 100);
12 |
13 | if (module && module.hot) {
14 | module.hot.accept(() => {
15 | require(module.id);
16 | });
17 | }
--------------------------------------------------------------------------------
/examples/example-emit-chunk/src/worker-size.js:
--------------------------------------------------------------------------------
1 | import { size } from './shared';
2 |
3 | if (self.interval) {
4 | clearInterval(self.interval);
5 | }
6 |
7 | self.interval = setInterval(() => {
8 | self.postMessage({
9 | size
10 | });
11 | }, 100);
12 |
13 | if (module && module.hot) {
14 | module.hot.accept(() => {
15 | require(module.id);
16 | });
17 | }
--------------------------------------------------------------------------------
/examples/example-globals/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "rm -rf target && rm -rf dist",
4 | "start": "node ../../lib/cli.js -c --content-base public --port 9001",
5 | "build": "npm run clean && rollup -c"
6 | },
7 | "devDependencies": {
8 | "rollup": "^2.44.0"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/example-globals/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/example-globals/rollup.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | input: './src/main.js',
3 | output: {
4 | dir: 'dist',
5 | format: 'iife',
6 | globals: {
7 | 'jquery': '$',
8 | 'maths': 'Math',
9 | 'underscore': '_'
10 | }
11 | },
12 | external: ['jquery', 'maths', 'underscore', 'document', 'location']
13 | }
--------------------------------------------------------------------------------
/examples/example-globals/src/main.js:
--------------------------------------------------------------------------------
1 | import jquery from 'jquery';
2 | import _ from 'underscore';
3 | import { max } from 'maths';
4 | import document, { querySelector } from 'document';
5 | import { pathname } from 'location';
6 |
7 | jquery('body').text('Hello World');
8 | console.log(max(1,2,3));
9 | console.log(_);
10 | console.log(document, querySelector);
11 | console.log(pathname);
--------------------------------------------------------------------------------
/examples/example-https/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/examples/example-https/cert/example.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDazCCAlOgAwIBAgIUIMfErMNBPuCIfdsR/WyADYLUUb0wDQYJKoZIhvcNAQEL
3 | BQAwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDA4MTIxNjAxMzBaFw0yMTA4
5 | MTIxNjAxMzBaMEUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
6 | HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
7 | AQUAA4IBDwAwggEKAoIBAQDd5JrdCQONbeXftekpeBWkvOnPB4wKuWaUhaYsR+oB
8 | IUu/MxOrNNxbECgPBUuAX/owwY0cHYQoWfy1l5BXRAOPOfBp33yYdT+IfTo+fhwf
9 | WsFfzZ/B4gOQr59YaU2W0T7gQJm0J8bQah4ze6v2GOGsN5GE/cIMj5TO5l3oyMch
10 | WyA3tMB2GYFHJ+e6a4jTfgUziB6jLE2zwDuR0GIdkB0ufY25m64Q7etlIr6X1iB1
11 | 0uqj3SVGBxPmiQAIDyfi/iyCJ7N9knH6vHwLc2Kubd7VnjrdbuyO7EGkVLKj7V3j
12 | P6+i/T3a+jqNUcpGzQVL134sha8DDAINBwSbkkyTQmkfAgMBAAGjUzBRMB0GA1Ud
13 | DgQWBBSJX15d/BXSJVuVOSrFzB4EezLZkDAfBgNVHSMEGDAWgBSJX15d/BXSJVuV
14 | OSrFzB4EezLZkDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC0
15 | 1G7EZo2CtZdDxpyjzZ17MVFi3jtQ+aGJg8Jsdfc5Jahmdwb4VEl7VcOsg3kHyfkk
16 | fi8sWPy877vXJtwUtgp0up7ABhM2gv1PW/p2s2CiPD1O9/KWzZCEWU4ZJY+hoWlI
17 | /13zyIQrsbUgaNBjWatutfkHrzjcUwVRexDOzzeG1tM+Alcn940PaYyiQEnHx3u4
18 | sRMkG5xu1skr+t3ONepNXENp6AfShQnbFgdewTf35kNOuW2wqU/mtroi8eaMNvD5
19 | a0nX3t/ZcyJjFslwr5IN2XkfXVVAhRgWH1H3B0EIA1p9OHfgkU5MPrceBKM5FqHl
20 | d4C1GBtjfJ58eGFP65th
21 | -----END CERTIFICATE-----
22 |
--------------------------------------------------------------------------------
/examples/example-https/cert/example.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDd5JrdCQONbeXf
3 | tekpeBWkvOnPB4wKuWaUhaYsR+oBIUu/MxOrNNxbECgPBUuAX/owwY0cHYQoWfy1
4 | l5BXRAOPOfBp33yYdT+IfTo+fhwfWsFfzZ/B4gOQr59YaU2W0T7gQJm0J8bQah4z
5 | e6v2GOGsN5GE/cIMj5TO5l3oyMchWyA3tMB2GYFHJ+e6a4jTfgUziB6jLE2zwDuR
6 | 0GIdkB0ufY25m64Q7etlIr6X1iB10uqj3SVGBxPmiQAIDyfi/iyCJ7N9knH6vHwL
7 | c2Kubd7VnjrdbuyO7EGkVLKj7V3jP6+i/T3a+jqNUcpGzQVL134sha8DDAINBwSb
8 | kkyTQmkfAgMBAAECggEAUqEvvkH+DauwH6epRSUPwkILO2pPDytNdN90Kyc4j+Ur
9 | RO8rUuUjbdHNaRKKtCqJi6B4ANFJBpHEFodzW9vbC3oC+hocVtXKq/QePWlO5aox
10 | OpDi/htBQp7z0sOb/h67sFy+ICVz1ua9imRye/l2CGDFEuZAXdcWDhohPG+QH+4S
11 | oIrx4EdC9PHQFpXLWCPBgvc3y3oK5FZ6dSK64rCyPWq1JpMRO9eDCAG7fxSfkTtn
12 | G4KpdJkbIpaMu02ZQbx+u9KGdoGodZxWsUhiGP1Ss75skrBQ5jwpXkBLVLxecdUR
13 | HMym4okqAwiEji8iDhHbYOpRSAa7yvPs988AaTD3+QKBgQD4NTg3mRSZLBMizpen
14 | 0e8S4KSkt4eMDbjrF0xmdnlIk/JRe3RWgnw4S06iHZnvyEyVxtTCNaKH1HW0FmH3
15 | HwvCqibin0F4OJyrNUmMviZQ0frgkhTcxBL24gf7MqkLZAVUfRMUI0YN+LWU9XE/
16 | d5NPeeOWgEi6GlTW9gEBL7XO3QKBgQDk2+ZGxAVYQk3qoJjauTXPJPe7+h76X2Mp
17 | 9vEd/c0RZolnd7qmdeapG/s+LBYAQlTOaG6j9fajrO/8MVhi4af+svjUjMoZmiEd
18 | AYQwiKiGOu2AMTkmRBO76sMF9QqDCn5kfDyNUJxoyf85dC/52GETownLXcQBuRxa
19 | /F6om7+yKwKBgDXa9lO592o00gIfaXCUcJdb/t1upQ6Se6km6QDie/Zvg4iCjP+o
20 | WGOuk2VBnwEUKOnmtotdL+LhCpkEskT4i4i1erJ5c68uOXA8o7TSHWYz6YeIqtYs
21 | BSFkce7jUyKRlUZ7zQP1k2G8sUmc5/GpdGEuRV+GfkFDLV1nC1jCjWRRAoGBALwN
22 | 5d7oW+v2L8hIRtITtp+MJPUVxja+AuIMxHx8fPF7bBTVUU5PO0Zic8TWvQ1hdAZX
23 | 0fJwvkYoaNq2QYafHkgwED+3oKoA+Iy5HkIzSSdjbJ0V1XwYHI8hjTdr70NWcB1p
24 | 6OfGAYAp28r+Kh4HK13TbszpM5Km5SHulmm98XsZAoGBAJcY1LwMD6U5G4Nj4Kuo
25 | A9qAaVAzu8OM0tGObngvsk7WVXGZtf6lI+FQUk/NtrHgr9kuIklbtuaP4L1wpfby
26 | hSDJUdDr8vtr69KcLnh1UuMm3S/p29teCD7mVc93jUgKcia2v3NUZgbofdTgpovn
27 | qbOfC3/9c/KAeiJQ3Chh816w
28 | -----END PRIVATE KEY-----
29 |
--------------------------------------------------------------------------------
/examples/example-https/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "rm -rf dist",
4 | "start": "node ../../lib/cli.js -c --content-base public --port 9001 --https --cert cert/example.crt --key cert/example.key",
5 | "build": "npm run clean && rollup -c"
6 | },
7 | "devDependencies": {
8 | "rollup": "^2.44.0"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/example-https/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Https dev server
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/example-https/rollup.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | input: './src/main.js',
3 | output: {
4 | dir: 'dist',
5 | format: 'esm',
6 | entryFileNames: '[name].js',
7 | assetFileNames: '[name][extname]'
8 | },
9 | }
--------------------------------------------------------------------------------
/examples/example-https/src/main.js:
--------------------------------------------------------------------------------
1 | document.body.textContent = 'Hello from https';
--------------------------------------------------------------------------------
/examples/example-live-bindings/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "rm -rf target && rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --content-base public --port 9001 --hot --live-bindings",
5 | "start:reference": "cross-env NODE_ENV=development node ../../lib/cli.js -c --content-base public --port 9001 --hot --live-bindings reference",
6 | "start:with-scope": "cross-env NODE_ENV=development node ../../lib/cli.js -c --content-base public --port 9001 --hot --live-bindings with-scope"
7 | },
8 | "devDependencies": {
9 | "cross-env": "^7.0.3"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/example-live-bindings/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Live Bindings
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/example-live-bindings/rollup.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | input: './src/main.js',
3 | output: {
4 | dir: 'dist',
5 | format: 'esm',
6 | entryFileNames: '[name].js'
7 | }
8 | }
--------------------------------------------------------------------------------
/examples/example-live-bindings/src/counter.js:
--------------------------------------------------------------------------------
1 | export var counter = 0;
2 |
3 | export function increment () {
4 | counter++;
5 | }
--------------------------------------------------------------------------------
/examples/example-live-bindings/src/main.js:
--------------------------------------------------------------------------------
1 | import { counter, increment } from './counter';
2 |
3 | setInterval(() => {
4 | increment();
5 | document.body.textContent = 'Counter: ' + counter;
6 | }, 1000);
--------------------------------------------------------------------------------
/examples/example-mjs-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "rm -rf target && rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --content-base public --port 9001 --hot",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c"
6 | },
7 | "devDependencies": {
8 | "cross-env": "^7.0.3",
9 | "rollup": "^2.44.0"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/example-mjs-config/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Rollup Config MJS
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/example-mjs-config/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | input: './src/main.js',
3 | output: {
4 | dir: 'dist',
5 | format: 'esm',
6 | entryFileNames: '[name].js'
7 | }
8 | }
--------------------------------------------------------------------------------
/examples/example-mjs-config/src/main.js:
--------------------------------------------------------------------------------
1 | document.body.textContent = 'Hello World!';
--------------------------------------------------------------------------------
/examples/example-multi-bundle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "rm -rf target && rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --content-base public --port 9001 --hot",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c"
6 | },
7 | "devDependencies": {
8 | "cross-env": "^7.0.3",
9 | "rollup": "^2.73.0",
10 | "rollup-plugin-terser": "^7.0.2"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/example-multi-bundle/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Multi-Bundle
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/example-multi-bundle/rollup.config.js:
--------------------------------------------------------------------------------
1 | let terser = require('rollup-plugin-terser').terser;
2 |
3 | let terser_options = {
4 | compress: {
5 | dead_code: true,
6 | global_defs: {
7 | module: false
8 | }
9 | }
10 | };
11 |
12 | module.exports = [{
13 | input: './src/entry-a.js',
14 | output: {
15 | dir: 'dist',
16 | format: 'esm',
17 | entryFileNames: '[name].js'
18 | },
19 | plugins: [
20 | process.env.NODE_ENV === 'production' && terser(terser_options)
21 | ]
22 | }, {
23 | input: './src/entry-b.js',
24 | output: {
25 | dir: 'dist',
26 | format: 'esm',
27 | entryFileNames: '[name].js'
28 | },
29 | plugins: [
30 | process.env.NODE_ENV === 'production' && terser(terser_options)
31 | ]
32 | }];
--------------------------------------------------------------------------------
/examples/example-multi-bundle/src/entry-a.js:
--------------------------------------------------------------------------------
1 | import Message, { module_id } from './message-a';
2 |
3 | let el = document.createElement('div');
4 | el.textContent = Message;
5 | document.body.appendChild(el);
6 |
7 | if (module && module.hot) {
8 | module.hot.accept(() => {
9 | el.textContent = require(module_id).default;
10 | });
11 | }
--------------------------------------------------------------------------------
/examples/example-multi-bundle/src/entry-b.js:
--------------------------------------------------------------------------------
1 | import Message, { module_id } from './message-b';
2 |
3 | let el = document.createElement('div');
4 | el.textContent = Message;
5 | document.body.appendChild(el);
6 |
7 | if (module && module.hot) {
8 | module.hot.accept(() => {
9 | el.textContent = require(module_id).default;
10 | });
11 | }
--------------------------------------------------------------------------------
/examples/example-multi-bundle/src/message-a.js:
--------------------------------------------------------------------------------
1 | // Change this message to see HMR
2 | export default 'Hello from Message A';
3 |
4 | export let module_id = module && module.id;
--------------------------------------------------------------------------------
/examples/example-multi-bundle/src/message-b.js:
--------------------------------------------------------------------------------
1 | // Change this message to see HMR
2 | export default 'Hello from Message B';
3 |
4 | export let module_id = module && module.id;
--------------------------------------------------------------------------------
/examples/example-preact/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["babel-preset-preact"],
3 | "env": {
4 | "development": {
5 | "plugins": ["react-refresh/babel"]
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/examples/example-preact/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "shx rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --hot --content-base public --port 9001",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c"
6 | },
7 | "dependencies": {
8 | "preact": "^10.5.13"
9 | },
10 | "devDependencies": {
11 | "@babel/core": "^7.13.14",
12 | "@prefresh/nollup": "^3.0.0",
13 | "@rollup/plugin-babel": "^5.3.0",
14 | "@rollup/plugin-node-resolve": "^11.2.1",
15 | "babel-preset-preact": "^2.0.0",
16 | "cross-env": "^7.0.3",
17 | "react-refresh": "^0.9.0",
18 | "rollup": "^2.44.0",
19 | "rollup-plugin-hot-css": "^0.2.1",
20 | "rollup-plugin-static-files": "^0.2.0",
21 | "rollup-plugin-terser": "^7.0.2",
22 | "shx": "^0.3.2"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/example-preact/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | HMR Test
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/example-preact/rollup.config.js:
--------------------------------------------------------------------------------
1 | import node_resolve from '@rollup/plugin-node-resolve';
2 | import babel from '@rollup/plugin-babel';
3 | import hotcss from 'rollup-plugin-hot-css';
4 | import static_files from 'rollup-plugin-static-files';
5 | import { terser } from 'rollup-plugin-terser';
6 | import prefresh from '@prefresh/nollup';
7 |
8 | let config = {
9 | input: './src/main.js',
10 | output: {
11 | dir: 'dist',
12 | format: 'esm',
13 | entryFileNames: '[name].[hash].js',
14 | assetFileNames: '[name].[hash][extname]'
15 | },
16 | plugins: [
17 | hotcss({
18 | hot: process.env.NODE_ENV === 'development',
19 | file: 'styles.css'
20 | }),
21 | babel({
22 | exclude: 'node_modules/**'
23 | }),
24 | node_resolve(),
25 | process.env.NODE_ENV === 'development' && prefresh()
26 | ]
27 | }
28 |
29 | if (process.env.NODE_ENV === 'production') {
30 | config.plugins = config.plugins.concat([
31 | static_files({
32 | include: ['./public']
33 | }),
34 | terser()
35 | ]);
36 | }
37 |
38 | export default config;
39 |
--------------------------------------------------------------------------------
/examples/example-preact/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: blue;
3 | }
--------------------------------------------------------------------------------
/examples/example-preact/src/App.js:
--------------------------------------------------------------------------------
1 | import Counter from './Counter';
2 | import { Internal } from './Internal';
3 | import Switch from './Switch';
4 | import './App.css';
5 |
6 | let App = () => (
7 |
8 |
Hello World
9 |
10 |
11 |
12 |
13 | );
14 |
15 | export default App;
--------------------------------------------------------------------------------
/examples/example-preact/src/Counter.css:
--------------------------------------------------------------------------------
1 | .Counter {
2 | font-size: 40px;
3 | color: green;
4 | }
--------------------------------------------------------------------------------
/examples/example-preact/src/Counter.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'preact/hooks';
2 | import './Counter.css';
3 |
4 | export default function Counter () {
5 | let [ count, setCount ] = useState(0);
6 |
7 | useEffect(() => {
8 | let interval = setInterval(() => {
9 | setCount(c => c + 1);
10 | }, 200);
11 |
12 | return () => {
13 | clearInterval(interval)
14 | };
15 | }, []);
16 |
17 | return Counter: {count}
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-preact/src/Internal.js:
--------------------------------------------------------------------------------
1 | import Counter from './Counter';
2 |
3 | export const Internal = () => (
4 |
5 |
6 |
7 | );
--------------------------------------------------------------------------------
/examples/example-preact/src/Switch.css:
--------------------------------------------------------------------------------
1 | .Switch {
2 | display: inline-block;
3 | border: 1px solid black;
4 | background-color: red;
5 | color: white;
6 | padding: 8px 20px;
7 | }
8 |
9 | .Switch[data-active="true"] {
10 | background-color: green;
11 | }
--------------------------------------------------------------------------------
/examples/example-preact/src/Switch.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'preact';
2 | import './Switch.css';
3 |
4 | export default class Switch extends Component {
5 | constructor () {
6 | super();
7 |
8 | this.state = {
9 | value: true
10 | };
11 |
12 | this.onClick = this.onClick.bind(this);
13 | }
14 |
15 | onClick () {
16 | this.setState({
17 | value: !this.state.value
18 | });
19 | }
20 |
21 | render() {
22 | return (
23 |
24 | {this.state.value? 'On' : 'Off'}
25 |
26 | );
27 |
28 | }
29 | }
--------------------------------------------------------------------------------
/examples/example-preact/src/main.js:
--------------------------------------------------------------------------------
1 | import { render, h } from 'preact';
2 | import App from './App';
3 |
4 | window.h = h;
5 |
6 | let root = document.querySelector('#app');
7 | document.body.appendChild(root);
8 | render( , root);
--------------------------------------------------------------------------------
/examples/example-public-path/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-react"],
3 | "plugins": ["react-hot-loader/babel", "@babel/plugin-syntax-dynamic-import"]
4 | }
--------------------------------------------------------------------------------
/examples/example-public-path/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "shx rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --hot --content-base public --port 9001 --public-path client",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c"
6 | },
7 | "dependencies": {
8 | "react": "^17.0.2",
9 | "react-dom": "^17.0.2"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.13.14",
13 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
14 | "@babel/preset-react": "^7.13.13",
15 | "@rollup/plugin-babel": "^5.3.0",
16 | "@rollup/plugin-node-resolve": "^11.2.1",
17 | "cross-env": "^7.0.3",
18 | "react-hot-loader": "^4.12.11",
19 | "rollup": "^2.44.0",
20 | "rollup-plugin-commonjs-alternate": "^0.8.0",
21 | "rollup-plugin-hot-css": "^0.4.0",
22 | "rollup-plugin-static-files": "^0.2.0",
23 | "rollup-plugin-terser": "^7.0.2",
24 | "shx": "^0.3.2"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/example-public-path/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | HMR Test
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/example-public-path/rollup.config.js:
--------------------------------------------------------------------------------
1 | import node_resolve from '@rollup/plugin-node-resolve';
2 | import babel from '@rollup/plugin-babel';
3 | import hotcss from 'rollup-plugin-hot-css';
4 | import commonjs from 'rollup-plugin-commonjs-alternate';
5 | import static_files from 'rollup-plugin-static-files';
6 | import { terser } from 'rollup-plugin-terser';
7 |
8 | let config = {
9 | input: './src/main.js',
10 | output: {
11 | dir: 'dist/client',
12 | format: 'esm',
13 | entryFileNames: '[name].[hash].js',
14 | assetFileNames: '[name].[hash][extname]'
15 | },
16 | plugins: [
17 | hotcss({
18 | hot: process.env.NODE_ENV === 'development',
19 | filename: 'styles.css'
20 | }),
21 | babel({
22 | exclude: 'node_modules/**'
23 | }),
24 | node_resolve(),
25 | commonjs({
26 | define: {
27 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
28 | }
29 | })
30 | ]
31 | }
32 |
33 | if (process.env.NODE_ENV === 'production') {
34 | config.plugins = config.plugins.concat([
35 | static_files({
36 | include: ['./public'],
37 | publicPath: '/client'
38 | }),
39 | terser({
40 | compress: {
41 | global_defs: {
42 | module: false
43 | }
44 | }
45 | })
46 | ]);
47 | }
48 |
49 | export default config;
--------------------------------------------------------------------------------
/examples/example-public-path/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: red;
3 | }
--------------------------------------------------------------------------------
/examples/example-public-path/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './App.css';
3 |
4 | class App extends Component {
5 |
6 | constructor () {
7 | super();
8 |
9 | this.activeComponent = undefined;
10 | }
11 |
12 | componentDidMount () {
13 | import('./Counter').then(component => {
14 | this.activeComponent = component.default;
15 | this.forceUpdate();
16 | });
17 | }
18 |
19 | render () {
20 | let ActiveComponent = this.activeComponent;
21 |
22 | return (
23 |
24 |
Hello World
25 | {this.activeComponent &&
}
26 |
27 | );
28 |
29 | }
30 | }
31 |
32 | if (process.env.NODE_ENV === 'development') {
33 | App = require('react-hot-loader').hot(module)(App);
34 | }
35 |
36 | export default App;
--------------------------------------------------------------------------------
/examples/example-public-path/src/Counter.css:
--------------------------------------------------------------------------------
1 | .Counter {
2 | font-size: 40px;
3 | color: green;
4 | }
--------------------------------------------------------------------------------
/examples/example-public-path/src/Counter.js:
--------------------------------------------------------------------------------
1 | import React , { Component } from 'react';
2 | import './Counter.css';
3 |
4 | class Counter extends Component {
5 | constructor () {
6 | super();
7 |
8 | this.state = {
9 | count: 0
10 | };
11 | }
12 |
13 | componentDidMount() {
14 | this.interval = setInterval(() => {
15 | this.setState({
16 | count: this.state.count + 1
17 | })
18 | }, 200);
19 | }
20 |
21 | componentWillUnmount() {
22 | clearInterval(this.interval)
23 | }
24 |
25 | render() {
26 | return Counter: {this.state.count}
27 | }
28 | }
29 |
30 | if (process.env.NODE_ENV === 'development') {
31 | Counter = require('react-hot-loader').hot(module)(Counter);
32 | }
33 |
34 | export default Counter;
--------------------------------------------------------------------------------
/examples/example-public-path/src/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | let root = document.querySelector('#app');
6 | document.body.appendChild(root);
7 | ReactDOM.render( , root);
--------------------------------------------------------------------------------
/examples/example-react-esinstall/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["@babel/preset-react", {
3 | "runtime": "classic"
4 | }]],
5 | "env": {
6 | "development": {
7 | "plugins": ["react-refresh/babel"]
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/examples/example-react-esinstall/.gitignore:
--------------------------------------------------------------------------------
1 | .babel_cache
2 | web_modules
--------------------------------------------------------------------------------
/examples/example-react-esinstall/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "shx rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --hot --content-base public --port 9001",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c"
6 | },
7 | "devDependencies": {
8 | "@babel/core": "^7.13.14",
9 | "@babel/preset-react": "^7.13.13",
10 | "@rollup/plugin-babel": "^5.3.0",
11 | "cross-env": "^7.0.3",
12 | "esinstall": "^1.1.7",
13 | "react-refresh": "^0.9.0",
14 | "rollup": "^1.28.0",
15 | "rollup-plugin-hot-css": "^0.4.0",
16 | "rollup-plugin-react-refresh": "0.0.3",
17 | "rollup-plugin-static-files": "^0.2.0",
18 | "rollup-plugin-terser": "^7.0.2",
19 | "shx": "^0.3.2"
20 | },
21 | "dependencies": {
22 | "react": "^17.0.2",
23 | "react-dom": "^17.0.2"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/example-react-esinstall/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | HMR Test
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/example-react-esinstall/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from '@rollup/plugin-babel';
2 | import hotcss from 'rollup-plugin-hot-css';
3 | import static_files from 'rollup-plugin-static-files';
4 | import { terser } from 'rollup-plugin-terser';
5 | import refresh from 'rollup-plugin-react-refresh';
6 |
7 | // Simple plugin that pre-compiles web dependencies into
8 | // ESM compatible modules that can be easily imported without the need for additional plugins.
9 | // This will increase initial compilation time, but subsequent restarts will be faster.
10 | // This also compiles based on NODE_ENV as the end result may be different.
11 | function esinstall (mods) {
12 | let fs = require('fs');
13 | let es = require('esinstall');
14 |
15 | return {
16 | buildStart () {
17 | for (let i = 0; i < mods.length; i++) {
18 | if (!fs.existsSync('web_modules/' + process.env.NODE_ENV + '/' + mods[i] + '.js')) {
19 | console.log('[esinstall] Pre-compiling web modules...');
20 | return es.install(mods, { dest: 'web_modules/' + process.env.NODE_ENV });
21 | }
22 | }
23 | },
24 |
25 | resolveId (id) {
26 | if (!id.startsWith('.')) {
27 | let parts = id.split('/');
28 | if (mods.indexOf(parts[0]) > -1) {
29 | return {
30 | id: process.cwd() + '/web_modules/' + process.env.NODE_ENV + '/' + id + '.js'
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
38 | // Implements a caching layer over the babel code
39 | // Unfortunately it's not really possible for Nollup to implement a cold cache.
40 | // This is because the plugins are unpredictable and can have their own local state.
41 | // A lot of the performance loss is a result of babel however. So we can implement
42 | // a local cache layer here to reuse existing transformations.
43 | function babel_cache (opts) {
44 | let babel_plugin = babel(opts);
45 | let crypto = require('crypto');
46 | let fs = require('fs');
47 | let cache = {};
48 |
49 | if (fs.existsSync('.babel_cache')) {
50 | cache = JSON.parse(fs.readFileSync('.babel_cache', 'utf8'));
51 | }
52 |
53 | process.on('SIGINT', () => {
54 | fs.writeFileSync('.babel_cache', JSON.stringify(cache));
55 | process.exit();
56 | });
57 |
58 | return {
59 | ...babel_plugin,
60 | transform: async (code, id) => {
61 | // Transformations can change between dev/test/prod
62 | if (process.env.NODE_ENV !== 'development') {
63 | return babel_plugin.transform(code, id);
64 | }
65 |
66 | if (id.indexOf('node_modules') > -1 || id.indexOf('web_modules') > -1) {
67 | return null;
68 | }
69 |
70 | let key = id + '-' + crypto.createHash('md5').update(code).digest("hex");
71 | if (cache[key]) {
72 | return JSON.parse(cache[key]);
73 | }
74 |
75 | let result = await babel_plugin.transform(code, id);
76 | cache[key] = JSON.stringify(result);
77 | return result;
78 | }
79 | }
80 | }
81 |
82 | let config = {
83 | input: './src/main.js',
84 | output: {
85 | dir: 'dist',
86 | format: 'esm',
87 | entryFileNames: '[name].[hash].js',
88 | assetFileNames: '[name].[hash][extname]'
89 | },
90 | plugins: [
91 | hotcss({
92 | hot: process.env.NODE_ENV === 'development',
93 | filename: 'styles.css'
94 | }),
95 | babel_cache({
96 | babelHelpers: 'bundled',
97 | exclude: ['node_modules/**', 'web_modules/**']
98 | }),
99 | esinstall(['react', 'react-dom']),
100 | process.env.NODE_ENV === 'development' && refresh()
101 | ]
102 | }
103 |
104 | if (process.env.NODE_ENV === 'production') {
105 | config.plugins = config.plugins.concat([
106 | static_files({
107 | include: ['./public']
108 | }),
109 | terser()
110 | ]);
111 | }
112 |
113 | export default config;
--------------------------------------------------------------------------------
/examples/example-react-esinstall/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: blue;
3 | }
--------------------------------------------------------------------------------
/examples/example-react-esinstall/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Counter from './Counter';
3 | import { Internal } from './Internal';
4 | import './App.css';
5 |
6 | let App = () => (
7 |
8 |
Hello World
9 |
10 |
11 |
12 | );
13 |
14 | export default App;
15 |
--------------------------------------------------------------------------------
/examples/example-react-esinstall/src/Counter.css:
--------------------------------------------------------------------------------
1 | .Counter {
2 | font-size: 40px;
3 | color: green;
4 | }
--------------------------------------------------------------------------------
/examples/example-react-esinstall/src/Counter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useState, useEffect } from 'react';
3 | import './Counter.css';
4 |
5 | export default function Counter () {
6 | let [ count, setCount ] = useState(0);
7 |
8 | useEffect(() => {
9 | let interval = setInterval(() => {
10 | setCount(c => c + 1);
11 | }, 200);
12 |
13 | return () => {
14 | clearInterval(interval)
15 | };
16 | }, []);
17 |
18 | return Counter: {count}
19 | }
20 |
--------------------------------------------------------------------------------
/examples/example-react-esinstall/src/Internal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Counter from './Counter';
3 |
4 | export const Internal = () => (
5 |
6 |
7 |
8 | );
--------------------------------------------------------------------------------
/examples/example-react-esinstall/src/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | let root = document.querySelector('#app');
6 | document.body.appendChild(root);
7 | ReactDOM.render( , root);
--------------------------------------------------------------------------------
/examples/example-react-hot-loader/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-react"],
3 | "plugins": ["react-hot-loader/babel"]
4 | }
--------------------------------------------------------------------------------
/examples/example-react-hot-loader/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --hot --content-base public --port 9001",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c"
6 | },
7 | "dependencies": {
8 | "react": "^17.0.2",
9 | "react-dom": "^17.0.2"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.13.14",
13 | "@babel/preset-react": "^7.13.13",
14 | "@rollup/plugin-babel": "^5.3.0",
15 | "@rollup/plugin-node-resolve": "^11.2.1",
16 | "cross-env": "^7.0.3",
17 | "react-hot-loader": "^4.12.11",
18 | "rollup": "^2.44.0",
19 | "rollup-plugin-commonjs-alternate": "^0.8.0",
20 | "rollup-plugin-hot-css": "^0.4.0",
21 | "rollup-plugin-static-files": "^0.2.0",
22 | "rollup-plugin-terser": "^7.0.2"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/example-react-hot-loader/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | HMR Test
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/example-react-hot-loader/rollup.config.js:
--------------------------------------------------------------------------------
1 | import node_resolve from '@rollup/plugin-node-resolve';
2 | import babel from '@rollup/plugin-babel';
3 | import hotcss from 'rollup-plugin-hot-css';
4 | import commonjs from 'rollup-plugin-commonjs-alternate';
5 | import static_files from 'rollup-plugin-static-files';
6 | import { terser } from 'rollup-plugin-terser';
7 |
8 | let config = {
9 | input: './src/main.js',
10 | output: {
11 | dir: 'dist',
12 | format: 'esm',
13 | entryFileNames: '[name].[hash].js',
14 | assetFileNames: '[name].[hash][extname]'
15 | },
16 | plugins: [
17 | hotcss({
18 | hot: process.env.NODE_ENV === 'development',
19 | file: 'styles.css'
20 | }),
21 | babel({
22 | exclude: 'node_modules/**'
23 | }),
24 | node_resolve(),
25 | commonjs({
26 | define: {
27 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
28 | }
29 | })
30 | ]
31 | }
32 |
33 | if (process.env.NODE_ENV === 'production') {
34 | config.plugins = config.plugins.concat([
35 | static_files({
36 | include: ['./public']
37 | }),
38 | terser({
39 | compress: {
40 | global_defs: {
41 | module: false
42 | }
43 | }
44 | })
45 | ]);
46 | }
47 |
48 | export default config;
--------------------------------------------------------------------------------
/examples/example-react-hot-loader/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: blue;
3 | }
--------------------------------------------------------------------------------
/examples/example-react-hot-loader/src/App.js:
--------------------------------------------------------------------------------
1 | import Counter from './Counter';
2 | import { Internal } from './Internal';
3 | import Switch from './Switch';
4 | import './App.css';
5 | import React from 'react';
6 |
7 | let App = () => (
8 |
9 |
Hello World
10 |
11 |
12 |
13 |
14 | );
15 |
16 | if (process.env.NODE_ENV === 'development') {
17 | App = require('react-hot-loader').hot(module)(App);
18 | }
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/examples/example-react-hot-loader/src/Counter.css:
--------------------------------------------------------------------------------
1 | .Counter {
2 | font-size: 40px;
3 | color: green;
4 | }
--------------------------------------------------------------------------------
/examples/example-react-hot-loader/src/Counter.js:
--------------------------------------------------------------------------------
1 | import React , { Component } from 'react';
2 | import './Counter.css';
3 |
4 | export default class Counter extends Component {
5 | constructor () {
6 | super();
7 |
8 | this.state = {
9 | count: 0
10 | };
11 | }
12 |
13 | componentDidMount() {
14 | this.interval = setInterval(() => {
15 | this.setState({
16 | count: this.state.count + 1
17 | })
18 | }, 200);
19 | }
20 |
21 | componentWillUnmount() {
22 | clearInterval(this.interval)
23 | }
24 |
25 | render() {
26 | return Counter: {this.state.count}
27 | }
28 | }
--------------------------------------------------------------------------------
/examples/example-react-hot-loader/src/Internal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Counter from './Counter';
3 |
4 | export const Internal = () => (
5 |
6 |
7 |
8 | );
--------------------------------------------------------------------------------
/examples/example-react-hot-loader/src/Switch.css:
--------------------------------------------------------------------------------
1 | .Switch {
2 | display: inline-block;
3 | border: 1px solid black;
4 | background-color: red;
5 | color: white;
6 | padding: 8px 20px;
7 | }
8 |
9 | .Switch[data-active="true"] {
10 | background-color: green;
11 | }
--------------------------------------------------------------------------------
/examples/example-react-hot-loader/src/Switch.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Switch.css';
3 |
4 | export default class Switch extends Component {
5 | constructor () {
6 | super();
7 |
8 | this.state = {
9 | value: true
10 | };
11 |
12 | this.onClick = this.onClick.bind(this);
13 | }
14 |
15 | onClick () {
16 | this.setState({
17 | value: !this.state.value
18 | });
19 | }
20 |
21 | render() {
22 | return (
23 |
24 | {this.state.value? 'On' : 'Off'}
25 |
26 | );
27 |
28 | }
29 | }
--------------------------------------------------------------------------------
/examples/example-react-hot-loader/src/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | let root = document.querySelector('#app');
6 | document.body.appendChild(root);
7 | ReactDOM.render( , root);
--------------------------------------------------------------------------------
/examples/example-react-refresh/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["@babel/preset-react", {
3 | "runtime": "automatic"
4 | }]],
5 | "env": {
6 | "development": {
7 | "plugins": ["react-refresh/babel"]
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/examples/example-react-refresh/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "shx rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --hot --content-base public --port 9001",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c"
6 | },
7 | "dependencies": {
8 | "react": "^17.0.2",
9 | "react-dom": "^17.0.2"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.13.14",
13 | "@babel/preset-react": "^7.13.13",
14 | "@rollup/plugin-babel": "^5.3.0",
15 | "@rollup/plugin-node-resolve": "^13.3.0",
16 | "cross-env": "^7.0.3",
17 | "react-refresh": "^0.9.0",
18 | "rollup": "^2.77.0",
19 | "rollup-plugin-commonjs-alternate": "^0.8.0",
20 | "rollup-plugin-hot-css": "^0.4.0",
21 | "rollup-plugin-react-refresh": "0.0.3",
22 | "rollup-plugin-static-files": "^0.2.0",
23 | "rollup-plugin-terser": "^7.0.2",
24 | "shx": "^0.3.2"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/example-react-refresh/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | HMR Test
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/example-react-refresh/rollup.config.js:
--------------------------------------------------------------------------------
1 | import node_resolve from '@rollup/plugin-node-resolve';
2 | import babel from '@rollup/plugin-babel';
3 | import hotcss from 'rollup-plugin-hot-css';
4 | import commonjs from 'rollup-plugin-commonjs-alternate';
5 | import static_files from 'rollup-plugin-static-files';
6 | import { terser } from 'rollup-plugin-terser';
7 | import refresh from 'rollup-plugin-react-refresh';
8 |
9 | let config = {
10 | input: './src/main.js',
11 | output: {
12 | dir: 'dist',
13 | format: 'esm',
14 | entryFileNames: '[name].[hash].js',
15 | assetFileNames: '[name].[hash][extname]'
16 | },
17 | plugins: [
18 | hotcss({
19 | hot: process.env.NODE_ENV === 'development',
20 | filename: 'styles.css'
21 | }),
22 | babel({
23 | exclude: 'node_modules/**'
24 | }),
25 | node_resolve(),
26 | commonjs({
27 | define: {
28 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
29 | }
30 | }),
31 | process.env.NODE_ENV === 'development' && refresh()
32 | ]
33 | }
34 |
35 | if (process.env.NODE_ENV === 'production') {
36 | config.plugins = config.plugins.concat([
37 | static_files({
38 | include: ['./public']
39 | }),
40 | terser({
41 | compress: {
42 | global_defs: {
43 | module: false
44 | }
45 | }
46 | })
47 | ]);
48 | }
49 |
50 | export default config;
--------------------------------------------------------------------------------
/examples/example-react-refresh/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: blue;
3 | }
--------------------------------------------------------------------------------
/examples/example-react-refresh/src/App.js:
--------------------------------------------------------------------------------
1 | import Counter from './Counter';
2 | import { Internal } from './Internal';
3 | import './App.css';
4 |
5 | let App = () => (
6 |
7 |
Hello World
8 |
9 |
10 |
11 | );
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/examples/example-react-refresh/src/Counter.css:
--------------------------------------------------------------------------------
1 | .Counter {
2 | font-size: 40px;
3 | color: green;
4 | }
--------------------------------------------------------------------------------
/examples/example-react-refresh/src/Counter.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import './Counter.css';
3 |
4 | export default function Counter () {
5 | let [ count, setCount ] = useState(0);
6 |
7 | useEffect(() => {
8 | let interval = setInterval(() => {
9 | setCount(c => c + 1);
10 | }, 200);
11 |
12 | return () => {
13 | clearInterval(interval)
14 | };
15 | }, []);
16 |
17 | return Counter: {count}
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-react-refresh/src/Internal.js:
--------------------------------------------------------------------------------
1 | import Counter from './Counter';
2 |
3 | export const Internal = () => (
4 |
5 |
6 |
7 | );
--------------------------------------------------------------------------------
/examples/example-react-refresh/src/main.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom';
2 | import App from './App';
3 |
4 | let root = document.querySelector('#app');
5 | document.body.appendChild(root);
6 | ReactDOM.render( , root);
--------------------------------------------------------------------------------
/examples/example-single-file-bundle/.gitignore:
--------------------------------------------------------------------------------
1 | public/build
--------------------------------------------------------------------------------
/examples/example-single-file-bundle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "rm -rf dist",
4 | "start": "node ../../lib/cli.js -c --content-base public --port 9001",
5 | "build": "npm run clean && rollup -c"
6 | },
7 | "devDependencies": {
8 | "rollup": "^2.44.0"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/example-single-file-bundle/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Single File Bundle
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/example-single-file-bundle/rollup.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | input: './src/main.js',
3 | output: {
4 | // You shouldn't really be putting your bundles
5 | // into "public" because that's considered a source
6 | // directory. But because Rollup template projects insist
7 | // on doing this, Nollup has compatibility. Always
8 | // put your build output into a separate "dist" directory.
9 | file: 'public/build/bundle.js',
10 | format: 'esm'
11 | }
12 | }
--------------------------------------------------------------------------------
/examples/example-single-file-bundle/src/main.js:
--------------------------------------------------------------------------------
1 | import Message from './message';
2 |
3 | document.body.textContent = Message;
--------------------------------------------------------------------------------
/examples/example-single-file-bundle/src/message.js:
--------------------------------------------------------------------------------
1 | export default 'Hello World!';
--------------------------------------------------------------------------------
/examples/example-typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "shx rm -rf dist",
4 | "start": "cross-env NODE_ENV=development node ../../lib/cli.js -c --configPlugin typescript --hot --content-base public --port 9001",
5 | "build": "npm run clean && cross-env NODE_ENV=production rollup -c"
6 | },
7 | "devDependencies": {
8 | "@rollup/plugin-typescript": "^8.2.1",
9 | "cross-env": "^7.0.3",
10 | "rollup": "^2.46.0",
11 | "shx": "^0.3.2",
12 | "tslib": "^2.1.0",
13 | "typescript": "^4.2.3"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/example-typescript/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Typescript Test
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/example-typescript/rollup.config.ts:
--------------------------------------------------------------------------------
1 | import typescript from '@rollup/plugin-typescript';
2 | import { RollupOptions } from 'rollup';
3 |
4 | let config: RollupOptions = {
5 | input: './src/main.ts',
6 | output: {
7 | dir: 'dist',
8 | format: 'esm',
9 | entryFileNames: '[name].[hash].js',
10 | assetFileNames: '[name].[hash][extname]'
11 | },
12 | plugins: [
13 | typescript()
14 | ]
15 | }
16 |
17 | export default config;
--------------------------------------------------------------------------------
/examples/example-typescript/src/enum.ts:
--------------------------------------------------------------------------------
1 | export enum DIGITS {
2 | ZERO = 0,
3 | ONE = 1,
4 | TWO = 2
5 | }
--------------------------------------------------------------------------------
/examples/example-typescript/src/main.ts:
--------------------------------------------------------------------------------
1 | import { DIGITS } from './enum';
2 |
3 | function add (a: number, b: number) {
4 | return a + b;
5 | }
6 |
7 | let message: string = 'hello world';
8 |
9 | document.body.textContent = message + ', Adding 1 + 2 = ' + add(DIGITS.ONE, DIGITS.TWO);
--------------------------------------------------------------------------------
/examples/example-virtual-index-html/.gitignore:
--------------------------------------------------------------------------------
1 | dist
--------------------------------------------------------------------------------
/examples/example-virtual-index-html/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "clean": "rm -rf dist",
4 | "start": "node ../../lib/cli.js -c --content-base public --port 9001 --history-api-fallback --public-path client",
5 | "build": "npm run clean && rollup -c"
6 | },
7 | "devDependencies": {
8 | "@rollup/plugin-html": "^0.2.3",
9 | "rollup": "^2.44.0",
10 | "rollup-plugin-hot-css": "^0.4.0"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/example-virtual-index-html/rollup.config.js:
--------------------------------------------------------------------------------
1 | import html from '@rollup/plugin-html';
2 | import hotcss from 'rollup-plugin-hot-css';
3 |
4 | export default {
5 | input: './src/main.js',
6 | output: {
7 | dir: 'dist',
8 | format: 'esm',
9 | entryFileNames: '[name].[hash].js',
10 | assetFileNames: '[name].[hash][extname]'
11 | },
12 | plugins: [
13 | hotcss({
14 | fileName: 'style.css'
15 | }),
16 | html({
17 | publicPath: '/client/'
18 | })
19 | ]
20 | }
--------------------------------------------------------------------------------
/examples/example-virtual-index-html/src/main.css:
--------------------------------------------------------------------------------
1 | p {
2 | font-size: 24px;
3 | }
--------------------------------------------------------------------------------
/examples/example-virtual-index-html/src/main.js:
--------------------------------------------------------------------------------
1 | import './main.css';
2 |
3 | let el = document.createElement('p');
4 | el.textContent = 'HTML plugin generated the following:';
5 |
6 | let pre = document.createElement('pre');
7 | pre.textContent = new XMLSerializer().serializeToString(document);
8 |
9 | document.body.appendChild(el);
10 | document.body.appendChild(pre);
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es2020"
5 | },
6 | "exclude": ["node_modules", "coverage", "examples", "test"]
7 | }
--------------------------------------------------------------------------------
/lib/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | if (!process.env.ROLLUP_WATCH) {
4 | process.env.ROLLUP_WATCH = 'true';
5 | }
6 |
7 | if (!process.env.NOLLUP) {
8 | process.env.NOLLUP = 'true';
9 | }
10 |
11 | let path = require('path');
12 | let fs = require('fs');
13 | let devServer = require('./dev-server')
14 |
15 | // https://github.com/rollup/rollup/blob/master/cli/run/getConfigPath.ts#L34
16 | function findConfigFile() {
17 | let extensions = ['mjs', 'cjs', 'ts'];
18 | let files = fs.readdirSync(process.cwd());
19 |
20 | for (let ext of extensions) {
21 | let fileName = `rollup.config.${ext}`;
22 | if (files.includes(fileName)) {
23 | return fileName;
24 | }
25 | }
26 |
27 | return `rollup.config.js`;
28 | }
29 |
30 | let options = {
31 | config: path.normalize(process.cwd() + '/' + findConfigFile()),
32 | contentBase: './',
33 | historyApiFallback: false,
34 | hot: false,
35 | port: 8080,
36 | verbose: false,
37 | hmrHost: undefined,
38 | https: false,
39 | host: 'localhost',
40 | liveBindings: false,
41 | rc: undefined,
42 | configPlugin: []
43 | };
44 |
45 | function getValue (index) {
46 | let next = process.argv[index + 1];
47 | if (next && !next.startsWith('-')) {
48 | return next;
49 | }
50 |
51 | return '';
52 | }
53 |
54 | for (let i = 0; i < process.argv.length; i++) {
55 | let value;
56 | let key = process.argv[i];
57 |
58 | switch (key) {
59 | case '-c': case '--config':
60 | value = getValue(i);
61 | if (value) {
62 | options.config = path.normalize(path.resolve(process.cwd(), process.argv[i + 1]));
63 | }
64 | break;
65 |
66 | case '--configPlugin':
67 | value = getValue(i);
68 | if (value) {
69 | options.configPlugin.push(...value.split(','));
70 | } else {
71 | throw new Error('Missing plugin value.');
72 | }
73 | break;
74 |
75 | case '--rc':
76 | value = getValue(i);
77 | if (value) {
78 | options.rc = value;
79 | } else {
80 | throw new Error('Missing value for rc.');
81 | }
82 | break;
83 |
84 | case '--environment':
85 | value = getValue(i);
86 | if (value) {
87 | value.split(',').forEach(variable => {
88 | let delimiterIndex = variable.indexOf(':');
89 |
90 | if (delimiterIndex > -1) {
91 | let key = variable.substring(0, delimiterIndex);
92 | let value = variable.substring(delimiterIndex + 1);
93 | process.env[key] = value;
94 | } else {
95 | process.env[variable] = 'true';
96 | }
97 | });
98 | } else {
99 | throw new Error('Missing value for environment.');
100 | }
101 | break;
102 |
103 | case '--content-base':
104 | value = getValue(i);
105 | if (value) {
106 | options.contentBase = value;
107 | } else {
108 | throw new Error('Missing path for content base.');
109 | }
110 | break;
111 |
112 | case '--public-path':
113 | value = getValue(i);
114 | if (value) {
115 | options.publicPath = value;
116 | } else {
117 | throw new Error('Missing path for public path.');
118 | }
119 | break;
120 |
121 | case '--history-api-fallback':
122 | value = getValue(i);
123 | options.historyApiFallback = value || true;
124 | break;
125 |
126 | case '--hot':
127 | options.hot = true;
128 | break;
129 |
130 | case '--port':
131 | value = getValue(i);
132 | if (value) {
133 | options.port = parseInt(value);
134 | } else {
135 | throw new Error('Missing port number.');
136 | }
137 | break;
138 |
139 | case '--verbose':
140 | options.verbose = true;
141 | break;
142 |
143 | case '--hmr-host':
144 | value = getValue(i);
145 | if (value) {
146 | options.hmrHost = value;
147 | } else {
148 | throw new Error('Missing host for HMR host.');
149 | }
150 | break;
151 |
152 | case '--host':
153 | value = getValue(i);
154 | if (value) {
155 | options.host = value;
156 | } else {
157 | throw new Error('Missing host for host option.');
158 | }
159 | break;
160 |
161 | case '--https':
162 | options.https = true;
163 | break;
164 |
165 | case '--key':
166 | value = getValue(i);
167 | if (value) {
168 | options.key = value;
169 | } else {
170 | throw new Error('Missing path for private key to use with https.');
171 | }
172 | break;
173 |
174 | case '--cert':
175 | value = getValue(i);
176 | if (value) {
177 | options.cert = value;
178 | } else {
179 | throw new Error('Missing path for cert to use with https.');
180 | }
181 | break;
182 |
183 | case '--live-bindings':
184 | value = getValue(i);
185 | if (value) {
186 | if (value !== 'reference' && value !== 'with-scope') {
187 | throw new Error('Invalid value for live bindings');
188 | }
189 | options.liveBindings = value;
190 | } else {
191 | options.liveBindings = true;
192 | }
193 | break;
194 | }
195 | }
196 |
197 | devServer(options).catch(e => {
198 | console.error(e.message);
199 | process.exit(1);
200 | });
201 |
202 | // Needed for posix systems when used with npm-run-all.
203 | process.on('SIGTERM', () => {
204 | process.exit(0);
205 | });
--------------------------------------------------------------------------------
/lib/dev-server.js:
--------------------------------------------------------------------------------
1 | let fs = require('fs');
2 | let path = require('path');
3 | let http = require('http');
4 | let https = require('https');
5 | let express = require('express');
6 | let fallback = require('express-history-api-fallback');
7 | let proxy = require('express-http-proxy');
8 | let NollupDevMiddleware = require('./dev-middleware');
9 | let ConfigLoader = require('./impl/ConfigLoader');
10 | let app = express();
11 |
12 | async function loadRc (options, file) {
13 | let nollupRc;
14 |
15 | if (file.endsWith('.js')) {
16 | nollupRc = await ConfigLoader.load(path.resolve(process.cwd(), file));
17 | } else {
18 | nollupRc = JSON.parse(fs.readFileSync(file, 'utf8'));
19 | }
20 |
21 | return Object.assign({}, options, nollupRc);
22 | }
23 |
24 | async function devServer(options) {
25 | if (options.rc) {
26 | if (fs.existsSync(options.rc)) {
27 | options = await loadRc(options, options.rc);
28 | } else {
29 | throw new Error('File does not exist: ' + options.rc);
30 | }
31 | } else if (fs.existsSync('.nolluprc')) {
32 | options = await loadRc(options, '.nolluprc');
33 | } else if (fs.existsSync('.nolluprc.js')) {
34 | options = await loadRc(options, '.nolluprc.js');
35 | }
36 |
37 | if (options.before) {
38 | options.before(app);
39 | }
40 |
41 | let server
42 | if (options.https) {
43 | if (!(options.key && options.cert)) {
44 | throw new Error('Usage of https requires cert and key to be set.')
45 | }
46 | const key = fs.readFileSync(options.key)
47 | const cert = fs.readFileSync(options.cert)
48 | server = https.createServer({ key, cert }, app)
49 | } else {
50 | server = http.createServer(app)
51 | }
52 |
53 | if (options.headers) {
54 | app.all('*', (req, res, next) => {
55 | for (let prop in options.headers) {
56 | res.setHeader(prop, options.headers[prop]);
57 | }
58 |
59 | next();
60 | });
61 | }
62 |
63 | let configPlugin = typeof options.configPlugin === 'string'? [options.configPlugin] : options.configPlugin;
64 | let config = typeof options.config === 'string' ? await ConfigLoader.load(options.config, configPlugin || []) : options.config;
65 |
66 | let nollup = NollupDevMiddleware(app, config, {
67 | hot: options.hot,
68 | verbose: options.verbose,
69 | headers: options.headers,
70 | hmrHost: options.hmrHost,
71 | contentBase: options.contentBase,
72 | publicPath: options.publicPath,
73 | liveBindings: options.liveBindings
74 | }, server)
75 |
76 | app.use(nollup);
77 |
78 | if (options.proxy) {
79 | Object.keys(options.proxy).forEach(route => {
80 | let opts = options.proxy[route];
81 |
82 | if (typeof opts === 'string') {
83 | opts = { host: opts };
84 | }
85 |
86 | let { host, ...routeOptions } = opts;
87 |
88 | app.use(route, proxy(host, {
89 | proxyReqPathResolver: req => {
90 | return require('url').parse(req.originalUrl).path;
91 | },
92 | ...routeOptions
93 | }));
94 | });
95 | }
96 |
97 | app.use(express.static(options.contentBase, {
98 | index: options.historyApiFallback? false : 'index.html'
99 | }));
100 |
101 | if (options.after) {
102 | options.after(app);
103 | }
104 |
105 | if (options.historyApiFallback) {
106 | let entryPoint = typeof options.historyApiFallback === 'string'? options.historyApiFallback : 'index.html';
107 |
108 | let publicPath = options.publicPath || '/';
109 | if (!publicPath.startsWith('/')) {
110 | publicPath = '/' + publicPath;
111 | }
112 |
113 | if (!publicPath.endsWith('/')) {
114 | publicPath = publicPath + '/';
115 | }
116 |
117 | app.use((req, res, next) => {
118 | req.url = publicPath + entryPoint;
119 | nollup(req, res, next);
120 | });
121 |
122 | app.use(fallback(entryPoint, { root: options.contentBase }));
123 | }
124 |
125 | server.listen(options.port, options.host || 'localhost');
126 |
127 | console.log(`[Nollup] Listening on ${options.https ? 'https' : 'http'}://${options.host || 'localhost'}:${options.port}`);
128 | }
129 |
130 | module.exports = devServer
131 |
--------------------------------------------------------------------------------
/lib/impl/AcornParser.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | let acorn = require('acorn');
3 |
4 | let defaultAcornOptions = {
5 | ecmaVersion: 11,
6 | sourceType: 'module',
7 | preserveParens: false
8 | };
9 |
10 | let parser = acorn.Parser;
11 |
12 | /**
13 | * @param {string} input
14 | * @param {object} options
15 | * @return {any}
16 | */
17 | function parse (input, options = {}) {
18 | try {
19 | options = Object.assign({}, defaultAcornOptions, options)
20 | return parser.parse(input, options);
21 | } catch (e) {
22 | e.message = [
23 | e.message,
24 | ' ' + input.split('\n')[e.loc.line - 1],
25 | ' ' + '^'.padStart(e.loc.column + 1)
26 | ].join('\n');
27 |
28 | throw e;
29 | }
30 | }
31 |
32 | /**
33 | * @param {function|function[]} plugins
34 | */
35 | function inject (plugins) {
36 | if (typeof plugins === 'function') {
37 | plugins = [plugins];
38 | }
39 |
40 | plugins.forEach(plugin => {
41 | // @ts-ignore
42 | parser = acorn.Parser.extend(plugin);
43 | });
44 | }
45 |
46 | module.exports = { parse, inject };
--------------------------------------------------------------------------------
/lib/impl/ConfigLoader.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | let nollup = require('../index');
3 | let path = require('path');
4 |
5 | const RollupPluginPrefixes = ['rollup-plugin-', '@rollup/plugin-'];
6 |
7 | function getPlugins(values) {
8 | return values.map(moduleName => {
9 | if (!moduleName.startsWith('.') && !RollupPluginPrefixes.find(p => moduleName.startsWith(p))) {
10 | for (let i = 0; i < RollupPluginPrefixes.length; i++) {
11 | try {
12 | moduleName = require.resolve(`${RollupPluginPrefixes[i]}${moduleName}`, { paths: [process.cwd()] });
13 | break;
14 | } catch {
15 | continue;
16 | }
17 | }
18 | }
19 |
20 | return require(moduleName)();
21 | });
22 | }
23 |
24 | module.exports = class ConfigLoader {
25 | /**
26 | * Uses compiler to compile rollup.config.js
27 | * or dynamic import to directly load rollup.config.mjs
28 | *
29 | * @param {string} filepath
30 | * @param {string[]} configPlugins
31 | * @return {Promise}
32 | */
33 | static async load(filepath, configPlugins) {
34 |
35 | let bundle = await nollup({
36 | input: filepath,
37 | // If it isn't relative, it's probably a NodeJS import
38 | // so mark it as external so the require call persists.
39 | external: id => (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5, id.length) === '.json',
40 | // Support --configPlugin flag to transform this config file before loading
41 | plugins: getPlugins(configPlugins || [])
42 | });
43 |
44 | let isESM = filepath.endsWith('.mjs');
45 | let { output } = await bundle.generate({ format: isESM? 'es' : 'cjs' });
46 |
47 | let config = isESM?
48 | await ConfigLoader.loadESM(filepath, output[0].code) :
49 | await ConfigLoader.loadCJS(filepath, output[0].code);
50 |
51 | // When function, resolve
52 | return (typeof config === 'function')
53 | ? config()
54 | : config;
55 | }
56 |
57 | /**
58 | * Uses compiler to compile rollup.config.js file.
59 | * This allows config file to use ESM, but compiles to CJS
60 | * so that import statements change to require statements.
61 | *
62 | * @param {string} filepath
63 | * @param {string} code
64 | * @return {Promise}
65 | */
66 | static async loadCJS(filepath, code) {
67 | // Once transpiled, we temporarily modify the require function
68 | // so that when it loads the config file, it will load the transpiled
69 | // version instead, and all of the require calls inside that will still work.
70 | let defaultLoader = require.extensions['.js'];
71 | require.extensions['.js'] = (module, filename) => {
72 | if (filename === filepath) {
73 | // @ts-ignore
74 | module._compile(code, filename);
75 | } else {
76 | defaultLoader(module, filename);
77 | }
78 | };
79 |
80 | delete require.cache[filepath];
81 |
82 | // Load the config file. If it uses ESM export, it will
83 | // be exported with a default key, so get that. Otherwise
84 | // if it was written in CJS, use the root instead.
85 | let config = require(filepath);
86 | config = config.default || config;
87 | require.extensions['.js'] = defaultLoader;
88 |
89 | return config;
90 | }
91 |
92 | /**
93 | * Directly imports rollup.config.mjs
94 | *
95 | * @param {string} filepath
96 | * @param {string} code
97 | * @return {Promise}
98 | */
99 | static async loadESM(filepath, code) {
100 | let uri = `data:text/javascript;charset=utf-8,${encodeURIComponent(code)}`;
101 | return (await import(uri)).default;
102 | }
103 | };
104 |
--------------------------------------------------------------------------------
/lib/impl/ParseError.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | class ParseError extends Error {
3 | /**
4 | * @param {string} file
5 | * @param {Error} parent_error
6 | */
7 | constructor (file, parent_error) {
8 | if (parent_error instanceof ParseError) {
9 | throw parent_error;
10 | }
11 |
12 | let filename = file.replace(process.cwd(), '');
13 | let details = parent_error instanceof TypeError? parent_error.stack : parent_error;
14 | let message = filename + '\n' + details;
15 | super(message);
16 | this.name = 'ParseError';
17 | }
18 | }
19 |
20 | module.exports = ParseError;
--------------------------------------------------------------------------------
/lib/impl/PluginContainer.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | let NollupContext = require('./NollupContext');
3 | let PluginContext = require('./PluginContext');
4 | let PluginLifecycle = require('./PluginLifecycle');
5 | const RollupConfigContainer = require('./RollupConfigContainer');
6 | const PluginErrorHandler = require('./PluginErrorHandler');
7 |
8 | class PluginContainer {
9 | /**
10 | *
11 | * @param {RollupConfigContainer} config
12 | * @param {{parse: function(string, object): ESTree}} parser
13 | */
14 | constructor (config, parser) {
15 | this.__config = config;
16 | this.__meta = {};
17 |
18 | this.__currentModuleId = null;
19 | this.__currentMapChain = null;
20 | this.__currentOriginalCode = null;
21 | this.__currentLoadQueue = [];
22 | this.__parser = parser;
23 | this.__errorState = true;
24 |
25 | this.__onAddWatchFile = (source, parent) => {};
26 | this.__onGetWatchFiles = () => ([]);
27 | this.__onEmitFile = (referenceId, emittedFile) => {};
28 | this.__onGetFileName = (referenceId) => '';
29 | this.__onGetModuleIds = () => new Set().values();
30 | this.__onGetModuleInfo = (id) => ({});
31 | this.__onSetAssetSource = (id, source) => {};
32 | this.__onLoad = (resolvedId) => Promise.resolve();
33 |
34 | this.__errorHandler = new PluginErrorHandler();
35 | this.__errorHandler.onThrow(() => {
36 | this.__errorState = true;
37 | });
38 |
39 | this.__plugins = (config.plugins || []).map(plugin => ({
40 | execute: plugin,
41 | context: PluginContext.create(this, plugin),
42 | error: this.__errorHandler
43 | }));
44 |
45 | this.hooks = /** @type {PluginLifecycleHooks} */ (Object.entries(PluginLifecycle.create(this)).reduce((acc, val) => {
46 | if (val[0] === 'buildEnd' || val[0] === 'renderError' || val[0] === 'watchChange') {
47 | acc[val[0]] = val[1];
48 | return acc;
49 | }
50 |
51 | acc[val[0]] = (...args) => {
52 | if (this.__errorState) {
53 | throw new Error('PluginContainer "start()" method must be called before going further.');
54 | }
55 |
56 | // @ts-ignore
57 | return val[1](...args);
58 | }
59 |
60 | return acc;
61 | }, {}));
62 | }
63 |
64 | start () {
65 | this.__errorState = false;
66 | this.__errorHandler.reset();
67 | PluginLifecycle.resolveIdSkips.reset();
68 | }
69 |
70 | /**
71 | * Receives source and parent file if any.
72 | * @param {function(string, string): void} callback
73 | */
74 | onAddWatchFile (callback) {
75 | // Local copy of watch files for the getWatchFiles method, but also triggers this event
76 | this.__onAddWatchFile = callback;
77 | }
78 |
79 | /**
80 | * Must return a list of files that are being watched.
81 | *
82 | * @param {function(): string[]} callback
83 | */
84 | onGetWatchFiles (callback) {
85 | this.__onGetWatchFiles = callback;
86 | }
87 |
88 | /**
89 | * Receives emitted asset and chunk information.
90 | *
91 | * @param {function(string, RollupEmittedFile): void} callback
92 | */
93 | onEmitFile (callback) {
94 | this.__onEmitFile = callback;
95 | }
96 |
97 | /**
98 | * Receives the requested module. Must return module info.
99 | *
100 | * @param {function(string): object} callback
101 | */
102 | onGetModuleInfo (callback) {
103 | this.__onGetModuleInfo = callback;
104 | }
105 |
106 | /**
107 | * Receives referenceId for emitted file. Must return output file name.
108 | *
109 | * @param {function(string): string} callback
110 | */
111 | onGetFileName (callback) {
112 | this.__onGetFileName = callback;
113 | }
114 |
115 | /**
116 | * Receives asset reference id, and source.
117 | *
118 | * @param {function(string, string|Uint8Array): void} callback
119 | */
120 | onSetAssetSource (callback) {
121 | this.__onSetAssetSource = callback;
122 | }
123 |
124 | /**
125 | * Must return iterable of all modules in the current bundle.
126 | *
127 | * @param {function(): IterableIterator} callback
128 | */
129 | onGetModuleIds (callback) {
130 | this.__onGetModuleIds = callback;
131 | }
132 |
133 | /**
134 | * Must load the module.
135 | *
136 | * @param {function(): Promise} callback
137 | */
138 | onLoad (callback) {
139 | this.__onLoad = callback;
140 | }
141 | }
142 |
143 | module.exports = PluginContainer;
--------------------------------------------------------------------------------
/lib/impl/PluginErrorHandler.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | let { white, yellow } = require('./utils');
3 |
4 | // Formats the message as best it can.
5 | // Note that this diverges from Rollup warnings, which are formatted for the CLI only.
6 | // When using Rollup API by itself, it only prints normal warning message without all of the other properties like frame or position.
7 | // Nollup however has a dev-server, so it cannot take the CLI approach. Instead regardless of using CLI/API, it will be formatted.
8 | function format (error) {
9 | let output = '';
10 |
11 | if (typeof error === 'object') {
12 | if (error.pluginCode) {
13 | output += error.pluginCode + ': ';
14 | }
15 |
16 | if (error.message) {
17 | output += error.message;
18 | }
19 |
20 | if (!error.loc && error.filename) {
21 | let appendment = '';
22 | appendment += error.filename.replace(process.cwd(), '');
23 | if (error.start)
24 | appendment += ` (${error.start.line}:${error.start.column})`;
25 |
26 | output += '\n' + white(appendment);
27 | }
28 |
29 | if (error.loc && error.loc.file) {
30 | let appendment = '';
31 | appendment += error.loc.file.replace(process.cwd(), '');
32 | appendment += ` (${error.loc.line}:${error.loc.column})`;
33 | output += '\n' + white(appendment);
34 | }
35 |
36 | if (error.frame) {
37 | output += '\n' + white(error.frame);
38 | }
39 | } else {
40 | output += error;
41 | }
42 |
43 | if (error instanceof Error) {
44 | error.message = output;
45 | } else {
46 | error = new Error(output);
47 | }
48 |
49 | error.__isNollupError = true;
50 |
51 | return error;
52 | }
53 |
54 | class PluginErrorHandler {
55 | /**
56 | * @param {function} callback
57 | */
58 | onThrow (callback) {
59 | this.__onThrow = callback;
60 | }
61 |
62 | reset () {
63 | this.__errorThrown = false;
64 | }
65 |
66 | warn (e) {
67 | console.warn(yellow(format(e).message));
68 | }
69 |
70 | /**
71 | * @param {object|string} e
72 | * @return {void|never}
73 | */
74 | throw (e) {
75 | e = format(e);
76 |
77 | if (!this.__errorThrown) {
78 | this.__errorThrown = true;
79 | this.__onThrow();
80 |
81 | if (this.__asyncErrorListener) {
82 | this.__asyncErrorListener(e);
83 | } else {
84 | throw e;
85 | }
86 | }
87 | }
88 |
89 | /**
90 | * @param {Promise} promiseResult
91 | * @return {Promise}
92 | */
93 | async wrapAsync (promiseResult) {
94 | let errorPromise = new Promise(resolve => {
95 | this.__asyncErrorListener = resolve;
96 | });
97 |
98 | let result = await Promise.race([
99 | promiseResult,
100 | errorPromise
101 | ]).catch(e => {
102 | // Catches promises that resolve prior to
103 | // asyncErrorListener being instantiated.
104 | // Also catches errors that are async but
105 | // not triggered using .error()
106 | if (!this.__errorThrown) {
107 | this.__errorThrow = true;
108 | this.__onThrow();
109 | e = format(e);
110 | }
111 |
112 | return e;
113 | });
114 |
115 | if (result && result.__isNollupError) {
116 | this.__asyncErrorListener = undefined;
117 | throw result;
118 | }
119 |
120 | this.__asyncErrorListener = undefined;
121 | return result;
122 | }
123 | }
124 |
125 | module.exports = PluginErrorHandler;
--------------------------------------------------------------------------------
/lib/impl/PluginMeta.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | module.exports = {
3 | rollupVersion: '2.70',
4 | watchMode: true
5 | };
--------------------------------------------------------------------------------
/lib/impl/PluginUtils.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | let SourceMap = require('source-map');
3 | let SourceMapFast = require('source-map-fast');
4 | let PluginContainer = require('./PluginContainer');
5 |
6 | /**
7 | * @param {NollupTransformMapEntry[]} mapChain
8 | * @param {string} original_code
9 | * @param {string} filepath
10 | * @return {NollupTransformMapEntry[]}
11 | */
12 | function prepareSourceMapChain (mapChain, original_code, filepath) {
13 | mapChain = mapChain.filter(o => o.map && o.map.mappings).reverse();
14 |
15 | if (mapChain.length > 1) {
16 | mapChain.forEach((obj, index) => {
17 | obj.map.version = 3;
18 | obj.map.file = filepath + '_' + index;
19 | // Check is in place because some transforms return sources, in particular multi-file sources.
20 | obj.map.sources = obj.map.sources.length === 1? [filepath + '_' + (index + 1)] : obj.map.sources;
21 | if(obj.map.sourcesContent && obj.map.sourcesContent.length === 1) {
22 | obj.map.sourcesContent = [mapChain[index + 1] ? mapChain[index + 1].code : original_code]
23 | }
24 | });
25 | }
26 |
27 | return mapChain;
28 | }
29 |
30 | /**
31 | * @param {NollupTransformMapEntry[]} mapChain
32 | * @param {SourceMapGenerator} mapGenerator
33 | * @param {string} original_code
34 | * @param {string} filepath
35 | * @return {RollupSourceMap}
36 | */
37 | function generateSourceMap (mapChain, mapGenerator, original_code, filepath) {
38 | let map;
39 |
40 | if (mapChain.length > 1) {
41 | // @ts-ignore
42 | map = mapGenerator.toJSON();
43 | } else {
44 | map = mapChain.length > 0? mapChain[0].map : undefined;
45 | }
46 |
47 | if (map) {
48 | map.file = filepath;
49 | map.sources = [filepath];
50 | map.sourcesContent = [original_code];
51 | }
52 |
53 | // @ts-ignore
54 | return map;
55 | }
56 |
57 | /**
58 | * @param {NollupTransformMapEntry[]} inputMapChain
59 | * @param {string} original_code
60 | * @param {string} filepath
61 | * @return {RollupSourceMap}
62 | */
63 | function combineSourceMapChain (inputMapChain, original_code, filepath) {
64 | let mapGenerator, mapChain = prepareSourceMapChain(inputMapChain, original_code, filepath);
65 |
66 | if (mapChain.length > 1) {
67 | // @ts-ignore
68 | mapGenerator = SourceMap.SourceMapGenerator.fromSourceMap(new SourceMap.SourceMapConsumer(mapChain[0].map));
69 |
70 | for (let i = 1; i < mapChain.length; i++) {
71 | // @ts-ignore
72 | mapGenerator.applySourceMap(new SourceMap.SourceMapConsumer(mapChain[i].map), undefined, undefined);
73 | }
74 | }
75 |
76 | return generateSourceMap(mapChain, mapGenerator, original_code, filepath);
77 | }
78 |
79 | /**
80 | * @param {NollupTransformMapEntry[]} inputMapChain
81 | * @param {string} original_code
82 | * @param {string} filepath
83 | * @return {Promise}
84 | */
85 | async function combineSourceMapChainFast (inputMapChain, original_code, filepath) {
86 | let mapGenerator, mapChain = prepareSourceMapChain(inputMapChain, original_code, filepath);
87 |
88 | if (mapChain.length > 1) {
89 | mapGenerator = SourceMapFast.SourceMapGenerator.fromSourceMap(await new SourceMapFast.SourceMapConsumer(mapChain[0].map));
90 |
91 | for (let i = 1; i < mapChain.length; i++) {
92 | mapGenerator.applySourceMap(await new SourceMapFast.SourceMapConsumer(mapChain[i].map))
93 | }
94 | }
95 |
96 | return generateSourceMap(mapChain, mapGenerator, original_code, filepath);
97 | }
98 |
99 |
100 | /**
101 | * @param {PluginContainer} container
102 | * @param {string} id
103 | * @return {RollupModuleInfo}
104 | */
105 | function getModuleInfo (container, id) {
106 | let response = container.__onGetModuleInfo(id);
107 |
108 | return {
109 | id: id,
110 | code: response.code || null,
111 | isEntry: response.isEntry || false,
112 | isExternal: response.isExternal || false,
113 | importers: response.importers || [],
114 | importedIds: response.importedIds || [],
115 | importedIdResolutions: response.importedIdResolutions || [],
116 | meta: container.__meta[id] || {},
117 | dynamicImporters: response.dynamicImporters || [],
118 | dynamicallyImportedIds: response.dynamicallyImportedIds || [],
119 | dynamicallyImportedIdResolutions: response.dynamicallyImportedIdResolutions || [],
120 | ast: response.ast || null,
121 | hasModuleSideEffects: response.hasModuleSideEffects || false,
122 | syntheticNamedExports: response.syntheticNamedExports || false,
123 | implicitlyLoadedAfterOneOf: response.implicitlyLoadedAfterOneOf || [],
124 | implicitlyLoadedBefore: response.implicitlyLoadedBefore || [],
125 | hasDefaultExport: response.hasDefaultExport,
126 | isIncluded: response.isIncluded || false,
127 | moduleSideEffects: response.moduleSideEffects || true
128 | }
129 | }
130 |
131 | module.exports = { combineSourceMapChain, combineSourceMapChainFast, getModuleInfo }
--------------------------------------------------------------------------------
/lib/impl/RollupConfigContainer.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | let PluginMeta = require('./PluginMeta');
3 |
4 | /**
5 | * @param {RollupOptions} opts
6 | * @return {RollupOptions}
7 | */
8 | function applyDefaultOptions (opts) {
9 | return {
10 | input: opts.input,
11 | plugins: (opts.plugins || []).filter(p => Boolean(p)),
12 | external: opts.external || [],
13 | context: opts.context || undefined,
14 | moduleContext: opts.moduleContext || undefined
15 | };
16 | }
17 |
18 | /**
19 | * @param {RollupModuleFormat} format
20 | * @return {RollupModuleFormat}
21 | */
22 | function normalizeFormat (format) {
23 | if (format === 'esm' || format === 'module') {
24 | return 'es';
25 | }
26 |
27 | if (format === 'commonjs') {
28 | return 'cjs';
29 | }
30 |
31 | return format;
32 | }
33 |
34 | /**
35 | * @param {string} format
36 | */
37 | function validateOutputFormat (format) {
38 | const formats = ['es', 'cjs', 'iife', 'amd'];
39 |
40 | if (formats.indexOf(format) === -1) {
41 | throw new Error(`Invalid format "${format}". Only ${formats.join(', ')} supported.`);
42 | }
43 | }
44 |
45 | /**
46 | * @param {RollupOptions} options
47 | * @return {RollupOptions}
48 | */
49 | function callOptionsHook (options) {
50 | if (options.plugins) {
51 | options.plugins.forEach(plugin => {
52 | if (plugin && plugin.options) {
53 | options = plugin.options.call({
54 | meta: PluginMeta
55 | }, options) || options;
56 | }
57 | });
58 | }
59 |
60 | return options;
61 | }
62 |
63 | /**
64 | * @param {RollupPlugin[]} plugins
65 | * @param {RollupOutputOptions} outputOptions
66 | */
67 | function callOutputOptionsHook (plugins, outputOptions) {
68 | if (plugins) {
69 | plugins.forEach(plugin => {
70 | if (plugin.outputOptions) {
71 | outputOptions = plugin.outputOptions.call({
72 | meta: PluginMeta
73 | }, outputOptions) || outputOptions;
74 | }
75 | });
76 | }
77 |
78 | return outputOptions;
79 | }
80 |
81 | /**
82 | * @param {RollupInputOption} input
83 | * @return {string[]|Object}
84 | */
85 | function normalizeInput (input) {
86 | if (typeof input === 'string') {
87 | return [input];
88 | }
89 |
90 | if (Array.isArray(input)) {
91 | return input;
92 | }
93 |
94 | return input;
95 | }
96 |
97 | class RollupConfigContainer {
98 | /**
99 | * @param {RollupOptions} options
100 | */
101 | constructor (options) {
102 | options = applyDefaultOptions(options);
103 | options = callOptionsHook(options);
104 |
105 | this.input = normalizeInput(options.input);
106 | this.plugins = /** @type {RollupPlugin[]} */ (options.plugins);
107 | this.output = /** @type {RollupOutputOptions} */ (options.output || {});
108 | this.external = /** @type {RollupExternalOption} */ (options.external);
109 | this.acornInjectPlugins = /** @type {Function|Function[]} */ (options.acornInjectPlugins);
110 | this.context = /** @type {string} */ (options.context);
111 | this.moduleContext = /** @type {function(string):string | {[id: string]: string }} */ (options.moduleContext)
112 | }
113 |
114 | /**
115 | * @param {RollupOutputOptions} outputOptions
116 | */
117 | setOutputOptions (outputOptions) {
118 | outputOptions = callOutputOptionsHook(this.plugins, outputOptions);
119 |
120 | let output = {
121 | assetFileNames: 'assets/[name]-[hash][extname]',
122 | chunkFileNames: '[name]-[hash].js',
123 | entryFileNames: '[name].js',
124 | format: /** @type {RollupModuleFormat} */ ('es'),
125 | globals: {},
126 | ...outputOptions
127 | };
128 |
129 | if (output.format) {
130 | output.format = normalizeFormat(output.format);
131 | validateOutputFormat(output.format);
132 | }
133 |
134 | this.output = output;
135 | }
136 | }
137 |
138 | module.exports = RollupConfigContainer;
--------------------------------------------------------------------------------
/lib/impl/utils.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | let path = require('path');
3 | let NollupContext = require('./NollupContext');
4 |
5 | function white (text) {
6 | return '\x1b[1m\x1b[37m' + text + '\x1b[39m\x1b[22m';
7 | }
8 |
9 | function yellow (text) {
10 | return '\x1b[1m\x1b[33m' + text + '\x1b[39m\x1b[22m';
11 | }
12 |
13 | /**
14 | * @param {ESTree} node
15 | * @return {Array}
16 | */
17 | function findChildNodes (node) {
18 | let children = [];
19 |
20 | for (let prop in node) {
21 | if (Array.isArray(node[prop]) && node[prop][0] && node[prop][0].constructor && node[prop][0].constructor.name === 'Node') {
22 | children.push(...node[prop]);
23 | }
24 |
25 | if (node[prop] && node[prop].constructor && node[prop].constructor.name === 'Node') {
26 | children.push(node[prop]);
27 | }
28 | }
29 |
30 | return children;
31 | }
32 |
33 | /**
34 | * @param {string} target
35 | * @param {string} current
36 | * @return {string}
37 | */
38 | function resolvePath (target, current) {
39 | if (path.isAbsolute(target)) {
40 | return path.normalize(target);
41 | } else {
42 | // Plugins like CommonJS have namespaced imports.
43 | let parts = target.split(':');
44 | let namespace = parts.length === 2? parts[0] + ':' : '';
45 | let file = parts.length === 2? parts[1] : parts[0];
46 | let ext = path.extname(file);
47 |
48 | return namespace + path.normalize(path.resolve(path.dirname(current), ext? file : file + '.js'));
49 | }
50 | }
51 |
52 | /**
53 | * @param {string} format
54 | * @param {string} fileName
55 | * @param {string|function(RollupPreRenderedFile): string} pattern
56 | * @return {string}
57 | */
58 | function formatFileName (format, fileName, pattern) {
59 | let name = path.basename(fileName).replace(path.extname(fileName), '');
60 |
61 | if (typeof pattern === 'string') {
62 | return pattern.replace('[name]', name)
63 | .replace('[extname]', path.extname(fileName))
64 | .replace('[ext]', path.extname(fileName).substring(1))
65 | .replace('[format]', format === 'es'? 'esm' : format);
66 | }
67 |
68 | // TODO: Function pattern implementation
69 | return '';
70 | }
71 |
72 | /**
73 | * @param {string} file
74 | * @return {string}
75 | */
76 | function getNameFromFileName (file) {
77 | return path.basename(file.replace(/\0/g, '_')).replace(path.extname(file), '')
78 | }
79 |
80 | /**
81 | * @param {RollupOutputOptions} outputOptions
82 | * @param {RollupOutputFile[]} bundle
83 | * @param {NollupInternalEmittedAsset} asset
84 | * @param {Object} bundleReferenceIdMap
85 | */
86 | function emitAssetToBundle (outputOptions, bundle, asset, bundleReferenceIdMap) {
87 | let extensionlessName = getNameFromFileName(asset.name);
88 | let extension = path.extname(asset.name);
89 | let deconflictMatcher = new RegExp('^' + extensionlessName + '(\\d+)?' + extension + '$');
90 | let matches = bundle.filter(e => e.type === 'asset' && e.name.match(deconflictMatcher));
91 | let finalisedName = extensionlessName + (matches.length > 0? matches.length + 1 : '') + extension;
92 |
93 | let bundleEntry = {
94 | name: finalisedName,
95 | isAsset: /** @type {true} */ (true),
96 | type: /** @type {'asset'} */ ('asset'),
97 | source: asset.source,
98 | fileName: asset.fileName || formatFileName(outputOptions.format, finalisedName, outputOptions.assetFileNames)
99 | };
100 |
101 | bundleReferenceIdMap[asset.referenceId] = bundleEntry;
102 | bundle.push(bundleEntry);
103 | }
104 |
105 |
106 |
107 | module.exports = {
108 | white,
109 | yellow,
110 | resolvePath,
111 | formatFileName,
112 | getNameFromFileName,
113 | emitAssetToBundle,
114 | findChildNodes
115 | };
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | let NollupContext = require('./impl/NollupContext');
2 | let NollupCompiler = require('./impl/NollupCompiler');
3 |
4 | async function nollup (options = {}) {
5 | let queue = [];
6 | let processing = false;
7 | let context = await NollupContext.create(options);
8 |
9 | async function generateImpl (resolve, reject) {
10 | processing = true;
11 |
12 | try {
13 | resolve(await NollupCompiler.compile(context));
14 | } catch (e) {
15 | processing = false;
16 | reject(e);
17 | }
18 |
19 | processing = false;
20 |
21 | if (queue.length > 0) {
22 | queue.shift()();
23 | }
24 | }
25 |
26 | return {
27 | configure (opts = {}) {
28 | if (opts.liveBindings) {
29 | context.liveBindings = opts.liveBindings === true? 'reference' : opts.liveBindings;
30 | }
31 | },
32 |
33 | invalidate (file) {
34 | context.invalidate(file);
35 | },
36 |
37 | generate (outputOptions = {}) {
38 | context.setOutputOptions(outputOptions);
39 |
40 | return new Promise((resolve, reject) => {
41 | if (processing) {
42 | queue.push(() => generateImpl(resolve, reject));
43 | } else {
44 | generateImpl(resolve, reject);
45 | }
46 | });
47 | }
48 | };
49 |
50 | };
51 |
52 | module.exports = nollup;
--------------------------------------------------------------------------------
/lib/sdk.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | RollupConfigContainer: require('./impl/RollupConfigContainer'),
3 | PluginContainer: require('./impl/PluginContainer')
4 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nollup",
3 | "description": "Rollup-compatible bundler for development.",
4 | "version": "0.21.0",
5 | "main": "lib/index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/PepsRyuu/nollup.git"
9 | },
10 | "homepage": "https://github.com/PepsRyuu/nollup",
11 | "scripts": {
12 | "test": "mocha-istanbul-ui \"test/cases/**/*.js\" --instrument --console --once",
13 | "test:ui": "mocha-istanbul-ui \"test/cases/**/*.js\" --instrument --watch",
14 | "test:headless": "xvfb-run npm run test",
15 | "test:mocha": "mocha \"test/cases/**/*.js\""
16 | },
17 | "bin": {
18 | "nollup": "./lib/cli.js"
19 | },
20 | "dependencies": {
21 | "@rollup/pluginutils": "^3.0.8",
22 | "acorn": "^8.1.0",
23 | "chokidar": "^3.5.1",
24 | "convert-source-map": "^1.5.1",
25 | "express": "^4.16.3",
26 | "express-history-api-fallback": "^2.2.1",
27 | "express-http-proxy": "^1.5.1",
28 | "magic-string": "^0.25.7",
29 | "mime-types": "^2.1.29",
30 | "source-map": "^0.5.6",
31 | "source-map-fast": "npm:source-map@0.7.3",
32 | "ws": "^7.5.3"
33 | },
34 | "devDependencies": {
35 | "chai": "^4.3.4",
36 | "mocha": "^8.3.0",
37 | "mocha-istanbul-ui": "^0.4.1",
38 | "proxyquire": "^2.0.1",
39 | "requirejs": "^2.3.6",
40 | "rollup": "^2.79.1"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/test/cases/RequireUsage.js:
--------------------------------------------------------------------------------
1 | let { nollup, fs, expect, rollup } = require('../nollup');
2 | let Evaluator = require('../utils/evaluator');
3 |
4 | describe('Require Usage', () => {
5 | it ('should not throw error if using require with string in CJS format', async () => {
6 | fs.stub('./src/main.js', () => 'module.exports = require("path").resolve.toString()');
7 |
8 | let bundle = await nollup({
9 | input: './src/main.js'
10 | });
11 |
12 | let { output } = await bundle.generate({ format: 'cjs' });
13 | let { exports } = await Evaluator.init('cjs', 'main.js', output);
14 | expect(exports).not.to.be.undefined;
15 | fs.reset();
16 | });
17 |
18 | it ('should throw error if using require with string in CJS format and it doesn\'t exist', async () => {
19 | fs.stub('./src/main.js', () => 'require("fake")');
20 |
21 | let bundle = await nollup({
22 | input: './src/main.js'
23 | });
24 |
25 | let { output } = await bundle.generate({ format: 'cjs' });
26 | let passed;
27 |
28 | try {
29 | await Evaluator.init('cjs', 'main.js', output);
30 | passed = false;
31 | } catch (e) {
32 | passed = true;
33 | expect(e.indexOf('Cannot find module') > -1).to.be.true;
34 | } finally {
35 | fs.reset();
36 | expect(passed).to.be.true;
37 | }
38 | });
39 |
40 | it ('should throw error if using require with string in non-CJS format', async () => {
41 | fs.stub('./src/main.js', () => 'require("hello")');
42 |
43 | let bundle = await nollup({
44 | input: './src/main.js'
45 | });
46 |
47 | let { output } = await bundle.generate({ format: 'esm' });
48 |
49 | let passed;
50 | try {
51 | await Evaluator.init('esm', 'main.js', output);
52 | passed = false;
53 | } catch (e) {
54 | passed = true;
55 | expect(e.indexOf('Module not found: hello') > -1).to.be.true;
56 | } finally {
57 | fs.reset();
58 | expect(passed).to.be.true;
59 | }
60 | });
61 |
62 | it ('should not throw error if using require on bundled id', async () => {
63 | fs.stub('./src/main.js', () => 'if (executed === false) {executed = true; require(0); }');
64 |
65 | let bundle = await nollup({
66 | input: './src/main.js'
67 | });
68 |
69 | let { output } = await bundle.generate({ format: 'esm' });
70 | let passed;
71 |
72 | try {
73 | let { globals } = await Evaluator.init('esm', 'main.js', output, { executed: false });
74 | expect(globals.executed).to.be.true;
75 | passed = true;
76 | } catch (e) {
77 | passed = false;
78 | } finally {
79 | fs.reset();
80 | expect(passed).to.be.true;
81 | }
82 | });
83 |
84 | it ('should not throw error if using require on bundled id in CJS format', async () => {
85 | let executed = false;
86 | fs.stub('./src/main.js', () => 'if (executed === false) {executed = true; require(0); }');
87 |
88 | let bundle = await nollup({
89 | input: './src/main.js'
90 | });
91 |
92 | let { output } = await bundle.generate({ format: 'cjs' });
93 | let passed;
94 |
95 | try {
96 | let { globals } = await Evaluator.init('cjs', 'main.js', output, { executed: false });
97 | passed = true;
98 | expect(globals.executed).to.be.true;
99 | } catch (e) {
100 | passed = false;
101 | } finally {
102 | fs.reset();
103 | expect(passed).to.be.true;
104 | }
105 | });
106 |
107 | it ('should throw error if using require and passing unrecognised id', async () => {
108 | fs.stub('./src/main.js', () => 'require(1)');
109 |
110 | let bundle = await nollup({
111 | input: './src/main.js'
112 | });
113 |
114 | let { output } = await bundle.generate({ format: 'esm' });
115 | let passed;
116 | try {
117 | await Evaluator.init('esm', 'main.js', output);
118 | } catch (e) {
119 | passed = true;
120 | expect(e.indexOf('Module not found: 1') > -1).to.be.true;
121 | } finally {
122 | fs.reset();
123 | expect(passed).to.be.true;
124 | }
125 | });
126 |
127 | it ('should throw error if using require and passing unrecognised id in CJS format', async () => {
128 | fs.stub('./src/main.js', () => 'require(1)');
129 |
130 | let bundle = await nollup({
131 | input: './src/main.js'
132 | });
133 |
134 | let { output } = await bundle.generate({ format: 'cjs' });
135 | let passed;
136 |
137 | try {
138 | await Evaluator.init('cjs', 'main.js', output);
139 | } catch (e) {
140 | passed = true;
141 | expect(e.indexOf('Module not found: 1') > -1).to.be.true;
142 | } finally {
143 | fs.reset();
144 | expect(passed).to.be.true;
145 | }
146 | });
147 | });
--------------------------------------------------------------------------------
/test/cases/StaticDynamicImportOptimisation.js:
--------------------------------------------------------------------------------
1 | let { nollup, fs, expect, rollup } = require('../nollup');
2 | let Evaluator = require('../utils/evaluator');
3 |
4 | describe ('Static Dynamic Import Optimisation', () => {
5 | it ('should provide second argument to require.dynamic to enable local module import if statically imported as well', async () => {
6 | // https://github.com/PepsRyuu/nollup/issues/204
7 | fs.stub('./src/other.js', () => `import('./msg').then(res => res)`)
8 | fs.stub('./src/msg.js', () => `export default 123;`);
9 | fs.stub('./src/main.js', () => `
10 | import msg from './msg';
11 | import('./msg').then(res => res);
12 | import('./other').then(res => res);
13 | `);
14 |
15 | let bundle = await nollup({
16 | input: './src/main.js'
17 | });
18 |
19 | let { output } = await bundle.generate({ format: 'esm' });
20 | let main = output.find(o => o.fileName === 'main.js');
21 | expect(main.code.indexOf('require.dynamic(\\\'other-[hash].js\\\', 2).then') > -1).to.be.true;
22 | expect(main.code.indexOf('require.dynamic(\\\'msg-[hash].js\\\', 1') > -1).to.be.true;
23 |
24 | let other = output.find(o => o.fileName === 'other-[hash].js');
25 | expect(other.code.indexOf('require.dynamic(\\\'msg-[hash].js\\\', 1') > -1).to.be.true;
26 |
27 | expect(output.length).to.equal(3);
28 | });
29 |
30 | it ('should not export chunk if the same dynamic import exists in the same chunk statically', async () => {
31 | // https://github.com/PepsRyuu/nollup/issues/204
32 | fs.stub('./src/msg.js', () => `export default 123;`);
33 | fs.stub('./src/main.js', () => `
34 | import msg from './msg';
35 | import('./msg').then(res => __bundle_output = res.default);
36 | `);
37 |
38 | let bundle = await nollup({
39 | input: './src/main.js'
40 | });
41 |
42 | let { output } = await bundle.generate({ format: 'esm' });
43 | let main = output.find(o => o.fileName === 'main.js');
44 | expect(main.code.indexOf('require.dynamic(\\\'\\\', 1') > -1).to.be.true;
45 | expect(output.length).to.equal(1);
46 | let { globals } = await Evaluator.init('esm', 'main.js', output, { __bundle_output: '' });
47 |
48 | // imports locally
49 | await new Promise(resolve => setTimeout(resolve, 1000));
50 | expect(globals.__bundle_output).to.equal(123);
51 | });
52 |
53 | it ('should not have issue adjusting require dynamic if asset exported when chunk is optimised out', async () => {
54 | // https://github.com/PepsRyuu/nollup/issues/212
55 | let emitted;
56 | fs.stub('./src/msg.js', () => `export default 123;`);
57 | fs.stub('./src/main.js', () => `
58 | import './msg';
59 | import('./msg').then(res => __bundle_output = res.default);
60 | `);
61 |
62 | let bundle = await nollup({
63 | input: './src/main.js',
64 | plugins: [{
65 | transform (code, id) {
66 | if (!emitted) {
67 | emitted = true;
68 | this.emitFile({
69 | type: 'asset',
70 | name: 'myasset',
71 | fileName: 'myasset.css',
72 | source: '.class{}'
73 | })
74 | }
75 | }
76 | }]
77 | });
78 |
79 | let { output } = await bundle.generate({ format: 'esm' });
80 | expect(output.length).to.equal(2);
81 | let { globals } = await Evaluator.init('esm', 'main.js', output, { __bundle_output: '' });
82 |
83 | // imports locally
84 | await new Promise(resolve => setTimeout(resolve, 1000));
85 | expect(globals.__bundle_output).to.equal(123);
86 | });
87 | });
--------------------------------------------------------------------------------
/test/cases/VirtualModules.js:
--------------------------------------------------------------------------------
1 | let { nollup, fs, expect, rollup } = require('../nollup');
2 | let Evaluator = require('../utils/evaluator');
3 |
4 | describe ('Virtual Modules', () => {
5 |
6 | it ('should allow virtual module example to work', async () => {
7 | fs.stub('./src/main.js', () => 'export { default } from "virtual-module";');
8 |
9 | let virtualModulePlugin = {
10 | resolveId ( source ) {
11 | if (source === 'virtual-module') {
12 | return source;
13 | }
14 | return null;
15 | },
16 | load ( id ) {
17 | if (id === 'virtual-module') {
18 | return 'export default "Virtual Module"';
19 | }
20 | return null;
21 | }
22 | };
23 |
24 | let bundle = await nollup({
25 | input: './src/main.js',
26 | plugins: [virtualModulePlugin]
27 | });
28 |
29 | let { output } = await bundle.generate({ format: 'esm' });
30 | let { exports } = await Evaluator.init('esm', 'main.js', output);
31 | expect(exports.default).to.equal('Virtual Module');
32 | });
33 |
34 | it ('should allow null byte in the resolved id', async () => {
35 | fs.stub('./src/main.js', () => 'export { default } from "virtual-module";');
36 |
37 | let virtualModulePlugin = {
38 | resolveId ( source ) {
39 | if (source === 'virtual-module') {
40 | return '\0' + source;
41 | }
42 | return null;
43 | },
44 | load ( id ) {
45 | if (id === '\0virtual-module') {
46 | return 'export default "Virtual Module"';
47 | }
48 | return null;
49 | }
50 | };
51 |
52 | let bundle = await nollup({
53 | input: './src/main.js',
54 | plugins: [virtualModulePlugin]
55 | });
56 |
57 | let { output } = await bundle.generate({ format: 'esm' });
58 | let { exports } = await Evaluator.init('esm', 'main.js', output);
59 | expect(exports.default).to.equal('Virtual Module');
60 | });
61 |
62 | it ('should allow prefix in the resolved id', async () => {
63 | fs.stub('./src/main.js', () => 'export { default } from "virtual-module";');
64 |
65 | let virtualModulePlugin = {
66 | resolveId ( source ) {
67 | if (source === 'virtual-module') {
68 | return '\0prefix:' + source;
69 | }
70 | return null;
71 | },
72 | load ( id ) {
73 | if (id === '\0prefix:virtual-module') {
74 | return 'export default "Virtual Module"';
75 | }
76 | return null;
77 | }
78 | };
79 |
80 | let bundle = await nollup({
81 | input: './src/main.js',
82 | plugins: [virtualModulePlugin]
83 | });
84 |
85 | let { output } = await bundle.generate({ format: 'esm' });
86 | let { exports } = await Evaluator.init('esm', 'main.js', output);
87 | expect(exports.default).to.equal('Virtual Module');
88 | });
89 |
90 | it ('should allow virtual module example to work for entry module', async () => {
91 | let virtualModulePlugin = {
92 | resolveId ( source ) {
93 | if (source === 'virtual-module') {
94 | return source;
95 | }
96 | return null;
97 | },
98 | load ( id ) {
99 | if (id === 'virtual-module') {
100 | return 'export default "Virtual Module"';
101 | }
102 | return null;
103 | }
104 | };
105 |
106 | let bundle = await nollup({
107 | input: 'virtual-module',
108 | plugins: [virtualModulePlugin]
109 | });
110 |
111 | let { output } = await bundle.generate({ format: 'esm' });
112 | expect(output[0].fileName).to.equal('virtual-module.js');
113 |
114 | let { exports } = await Evaluator.init('esm', 'virtual-module.js', output);
115 | expect(exports.default).to.equal('Virtual Module');
116 | });
117 |
118 | it ('should allow null byte in the resolved id for entry module', async () => {
119 | let virtualModulePlugin = {
120 | resolveId ( source ) {
121 | if (source === 'virtual-module') {
122 | return '\0' + source;
123 | }
124 | return null;
125 | },
126 | load ( id ) {
127 | if (id === '\0virtual-module') {
128 | return 'export default "Virtual Module"';
129 | }
130 | return null;
131 | }
132 | };
133 |
134 | let bundle = await nollup({
135 | input: 'virtual-module',
136 | plugins: [virtualModulePlugin]
137 | });
138 |
139 | let { output } = await bundle.generate({ format: 'esm' });
140 | expect(output[0].fileName).to.equal('_virtual-module.js');
141 |
142 | let { exports } = await Evaluator.init('esm', '_virtual-module.js', output);
143 | expect(exports.default).to.equal('Virtual Module');
144 | });
145 |
146 | it ('should allow prefix in the resolved id for entry module', async () => {
147 | let virtualModulePlugin = {
148 | resolveId ( source ) {
149 | if (source === 'virtual-module') {
150 | return '\0prefix:' + source;
151 | }
152 | return null;
153 | },
154 | load ( id ) {
155 | if (id === '\0prefix:virtual-module') {
156 | return 'export default "Virtual Module"';
157 | }
158 | return null;
159 | }
160 | };
161 |
162 | let bundle = await nollup({
163 | input: 'virtual-module',
164 | plugins: [virtualModulePlugin]
165 | });
166 |
167 | let { output } = await bundle.generate({ format: 'esm' });
168 | expect(output[0].fileName).to.equal('_prefix:virtual-module.js');
169 | let { exports } = await Evaluator.init('esm', '_prefix:virtual-module.js', output);
170 | expect(exports.default).to.equal('Virtual Module');
171 | });
172 | });
173 |
--------------------------------------------------------------------------------
/test/cases/misc.js:
--------------------------------------------------------------------------------
1 | let { nollup, fs, expect, rollup } = require('../nollup');
2 |
3 | describe ('Misc', () => {
4 | it ('should not add any private properties to plugins', async () => {
5 | fs.stub('./src/main.js', () => 'console.log("hello");');
6 |
7 | let myplugin = {
8 | transform: (code) => {
9 | return code.replace('hello', 'world');
10 | }
11 | };
12 |
13 | let bundle = await nollup({
14 | input: './src/main.js',
15 | plugins: [myplugin]
16 | });
17 |
18 | let { output } = await bundle.generate({ format: 'esm' });
19 | expect(output[0].code.indexOf('hello')).to.equal(-1);
20 | expect(output[0].code.indexOf('world') > -1).to.be.true;
21 | expect(Object.keys(myplugin).length).to.equal(1);
22 | });
23 | });
--------------------------------------------------------------------------------
/test/cases/options/context.js:
--------------------------------------------------------------------------------
1 | let { nollup, fs, rollup, expect } = require('../../nollup');
2 | let Evaluator = require('../../utils/evaluator');
3 |
4 | describe ('Options: context', () => {
5 | it ('should have default value for this keyword in modules', async () => {
6 | fs.stub('./src/main.js', () => 'var value = this; export default \'\' + value;');
7 |
8 | let bundle = await nollup({
9 | input: './src/main.js'
10 | });
11 |
12 | let { output } = await bundle.generate({
13 | file: 'output.js',
14 | format: 'esm'
15 | });
16 |
17 | let { exports } = await Evaluator.init('esm', 'output.js', output);
18 | expect(exports.default).to.equal('undefined');
19 | fs.reset();
20 | });
21 |
22 | it ('should have default value for this keyword when exported as default export directly', async () => {
23 | fs.stub('./src/main.js', () => 'export default \'\' + this;');
24 |
25 | let bundle = await nollup({
26 | input: './src/main.js'
27 | });
28 |
29 | let { output } = await bundle.generate({
30 | file: 'output.js',
31 | format: 'esm'
32 | });
33 |
34 | let { exports } = await Evaluator.init('esm', 'output.js', output);
35 | expect(exports.default).to.equal('undefined');
36 | fs.reset();
37 | });
38 |
39 | it ('should allow to override the value of this keyword using context option', async () => {
40 | fs.stub('./src/main.js', () => 'export default this;');
41 |
42 | let bundle = await nollup({
43 | input: './src/main.js',
44 | context: '{hello: "world"}'
45 | });
46 |
47 | let { output } = await bundle.generate({
48 | file: 'output.js',
49 | format: 'esm'
50 | });
51 |
52 | let { exports } = await Evaluator.init('esm', 'output.js', output);
53 | expect(exports.default.hello).to.equal('world');
54 | fs.reset();
55 | });
56 |
57 | it ('should allow to override the value of this keyword using context option - scenario 2', async () => {
58 | fs.stub('./src/main.js', () => 'export default (this).toString();');
59 |
60 | let bundle = await nollup({
61 | input: './src/main.js',
62 | context: 'Promise'
63 | });
64 |
65 | let { output } = await bundle.generate({
66 | file: 'output.js',
67 | format: 'esm'
68 | });
69 |
70 | let { exports } = await Evaluator.init('esm', 'output.js', output);
71 | expect(exports.default).to.equal('function Promise() { [native code] }');
72 | fs.reset();
73 | });
74 | });
--------------------------------------------------------------------------------
/test/cases/options/input.js:
--------------------------------------------------------------------------------
1 | let { nollup, fs, expect, rollup } = require('../../nollup');
2 |
3 | describe ('Options: Input', () => {
4 | it ('should throw error if not defined', async () => {
5 | let passed = false;
6 |
7 | try {
8 | await nollup({});
9 | passed = true;
10 | } catch (e) {
11 | expect(e.message).to.equal('Input option not defined');
12 | }
13 |
14 | expect(passed).to.be.false;
15 | })
16 |
17 | it ('should accept single input entry with filename as entry name', async () => {
18 | fs.stub('./src/main.js', () => 'export default 123');
19 |
20 | let bundle = await nollup({
21 | input: './src/main.js'
22 | });
23 |
24 | let { output } = await bundle.generate({
25 | format: 'esm'
26 | });
27 |
28 | expect(output[0].fileName).to.equal('main.js');
29 | expect(output[0].isEntry).to.be.true;
30 |
31 | fs.reset();
32 | });
33 |
34 | it ('should accept object as input with key as entry name', async () => {
35 | fs.stub('./src/main1.js', () => 'export default 123');
36 | fs.stub('./src/main2.js', () => 'export default 456');
37 |
38 | let bundle = await nollup({
39 | input: {
40 | a: './src/main1.js',
41 | b: './src/main2.js'
42 | }
43 | });
44 |
45 | let { output } = await bundle.generate({
46 | format: 'esm'
47 | });
48 |
49 | let file_a = output.find(o => o.fileName === 'a.js');
50 | expect(file_a.fileName).to.equal('a.js');
51 | expect(file_a.isEntry).to.be.true;
52 |
53 | let file_b = output.find(o => o.fileName === 'b.js');
54 | expect(file_b.fileName).to.equal('b.js');
55 | expect(file_b.isEntry).to.be.true;
56 |
57 | fs.reset();
58 | });
59 |
60 | it ('should support an array of inputs with filename as entry name', async () => {
61 | fs.stub('./src/main1.js', () => 'export default 123');
62 | fs.stub('./src/main2.js', () => 'export default 456');
63 |
64 | let bundle = await nollup({
65 | input: ['./src/main1.js', './src/main2.js']
66 | });
67 |
68 | let { output } = await bundle.generate({
69 | format: 'esm'
70 | });
71 |
72 | let main1 = output.find(o => o.fileName === 'main1.js');
73 | expect(main1.fileName).to.equal('main1.js');
74 | expect(main1.isEntry).to.be.true;
75 |
76 | let main2 = output.find(o => o.fileName === 'main2.js');
77 | expect(main2.fileName).to.equal('main2.js');
78 | expect(main2.isEntry).to.be.true;
79 |
80 | fs.reset();
81 | })
82 | });
--------------------------------------------------------------------------------
/test/cases/options/moduleContext.js:
--------------------------------------------------------------------------------
1 | let { nollup, fs, rollup, expect } = require('../../nollup');
2 | let path = require('path');
3 | let Evaluator = require('../../utils/evaluator');
4 |
5 | describe ('Options: moduleContext', () => {
6 | it ('should allow priortise moduleContext over context if it matches', async () => {
7 | fs.stub('./src/main1.js', () => 'export default this;');
8 | fs.stub('./src/main2.js', () => 'export default this;');
9 |
10 | let bundle = await nollup({
11 | input: ['./src/main1.js', './src/main2.js'],
12 | context: '{foo: "bar"}',
13 | moduleContext: (id) => {
14 | if (id.indexOf('main1') > -1) {
15 | return '{hello: "world"}'
16 | }
17 | }
18 | });
19 |
20 | let { output } = await bundle.generate({
21 | dir: 'dist',
22 | format: 'esm'
23 | });
24 |
25 | let { exports: exs1 } = await Evaluator.init('esm', 'main1.js', output);
26 | expect(exs1.default.hello).to.equal('world');
27 |
28 | let { exports: exs2 } = await Evaluator.init('esm', 'main2.js', output);
29 | expect(exs2.default.foo).to.equal('bar');
30 | fs.reset();
31 | });
32 |
33 | it ('should allow moduleContext to be an object', async () => {
34 | fs.stub('./src/main1.js', () => 'export default this;');
35 | fs.stub('./src/main2.js', () => 'export default this;');
36 |
37 | let bundle = await nollup({
38 | input: ['./src/main1.js', './src/main2.js'],
39 | context: '{foo: "bar"}',
40 | moduleContext: {
41 | [path.resolve(process.cwd(), './src/main1.js')]: '{"hello": "world"}'
42 | }
43 | });
44 |
45 | let { output } = await bundle.generate({
46 | dir: 'dist',
47 | format: 'esm'
48 | });
49 |
50 | let { exports: exs1 } = await Evaluator.init('esm', 'main1.js', output);
51 | expect(exs1.default.hello).to.equal('world');
52 |
53 | let { exports: exs2 } = await Evaluator.init('esm', 'main2.js', output);
54 | expect(exs2.default.foo).to.equal('bar');
55 | fs.reset();
56 | });
57 | });
--------------------------------------------------------------------------------
/test/cases/options/output-assetFileNames.js:
--------------------------------------------------------------------------------
1 | let { nollup, fs, expect, rollup } = require('../../nollup');
2 |
3 | describe ('Options: output.assetFileNames', () => {
4 | let bundle;
5 |
6 | beforeEach(async () => {
7 | fs.stub('./src/main.js', () => 'import "./style.css"; export default 123');
8 | fs.stub('./src/style.css', () => '*{color: blue}');
9 |
10 | bundle = await nollup({
11 | input: './src/main.js',
12 | plugins: [{
13 | transform (code, id) {
14 | if (id.endsWith('.css')) {
15 | this.emitAsset('style.css', code)
16 | return '';
17 | }
18 | }
19 | }]
20 | });
21 | })
22 |
23 | afterEach(() => {
24 | fs.reset();
25 | });
26 |
27 | it ('should default to assets/[name]-[hash][extname]', async () => {
28 | let { output } = await bundle.generate({
29 | format: 'esm'
30 | });
31 |
32 | let file = output.find(o => o.fileName.indexOf('style') > -1);
33 | expect(/assets\/style-(.*?)\.css/.test(file.fileName)).to.be.true;
34 | });
35 |
36 | it ('should support [ext]', async () => {
37 | let { output } = await bundle.generate({
38 | format: 'esm',
39 | assetFileNames: 'custom/[name].[ext]'
40 | });
41 |
42 | let file = output.find(o => o.fileName.indexOf('style') > -1);
43 | expect(/custom\/style\.css/.test(file.fileName)).to.be.true;
44 | });
45 |
46 | it ('assets emitted during generateBuild have proper hashed name', async () => {
47 | fs.stub('./src/main.js', () => 'export default 123');
48 |
49 | let bundle = await nollup({
50 | input: './src/main.js',
51 | plugins: [{
52 | generateBundle (output, bundle) {
53 | this.emitAsset('style.css', 'lol');
54 | }
55 | }]
56 | });
57 |
58 | let { output } = await bundle.generate({
59 | format: 'esm',
60 | assetFileNames: 'assets/[name]-hello[extname]'
61 | });
62 |
63 | expect(output.length).to.equal(2);
64 | expect(output[1].fileName).to.equal('assets/style-hello.css');
65 |
66 | fs.reset();
67 | });
68 | });
--------------------------------------------------------------------------------
/test/cases/options/output-chunkFileNames.js:
--------------------------------------------------------------------------------
1 | let { nollup, fs, expect, rollup } = require('../../nollup');
2 | let path = require('path');
3 |
4 | describe ('Options: output.chunkFileNames', () => {
5 | let bundle;
6 |
7 | beforeEach(async function () {
8 | fs.stub('./src/main.js', () => 'import("./dynamic.js"); export default 123');
9 | fs.stub('./src/dynamic.js', () => 'export default 456');
10 |
11 | bundle = await nollup({
12 | input: './src/main.js'
13 | });
14 | });
15 |
16 | afterEach(() => {
17 | fs.reset();
18 | });
19 |
20 | it ('should default to chunk-[hash].js', async () => {
21 | let { output } = await bundle.generate({
22 | format: 'esm'
23 | });
24 |
25 | expect(output.length).to.equal(2);
26 | expect(output.find(o => o.fileName === 'main.js').fileName).not.to.be.undefined;
27 | expect(output.find(o => o.fileName.match(/^dynamic\-(.*?).js$/) !== null)).not.to.be.undefined;
28 | });
29 |
30 | it ('should allow to be overrided', async () => {
31 | let { output } = await bundle.generate({
32 | format: 'esm',
33 | chunkFileNames: 'lol-[format].js'
34 | });
35 |
36 | expect(output.length).to.equal(2);
37 | expect(output.find(o => o.fileName === 'main.js').fileName).not.to.be.undefined;
38 | expect(output.find(o => o.fileName.match(/^lol\-esm.js$/) !== null)).not.to.be.undefined;
39 | });
40 |
41 | it ('should be used as the import name', async () => {
42 | let { output } = await bundle.generate({
43 | format: 'esm',
44 | chunkFileNames: 'lol-[format].js'
45 | });
46 |
47 | let file = output.find(o => o.fileName === 'main.js');
48 | let importId = path.resolve(process.cwd(), './src/dynamic.js');
49 | expect(file.code.indexOf(`require.dynamic(\\'lol-esm.js`) > -1).to.be.true;
50 | });
51 |
52 | it ('should use "esm" as format for "es" output', async () => {
53 | let { output } = await bundle.generate({
54 | format: 'es',
55 | chunkFileNames: 'lol-[format].js'
56 | });
57 |
58 | expect(output.length).to.equal(2);
59 | expect(output.find(o => o.fileName === 'main.js').fileName).not.to.be.undefined;
60 | expect(output.find(o => o.fileName.match(/^lol-esm.js$/) !== null)).not.to.be.undefined;
61 | });
62 | });
--------------------------------------------------------------------------------
/test/cases/options/output-dir.js:
--------------------------------------------------------------------------------
1 | let { nollup, fs } = require('../../nollup');
2 |
3 | describe ('Options: output.dir', () => {
4 | it ('should allow output.dir to be defined')
5 | });
--------------------------------------------------------------------------------
/test/cases/options/output-entryFileNames.js:
--------------------------------------------------------------------------------
1 | let { nollup, fs, expect, rollup } = require('../../nollup');
2 |
3 | describe ('Options: output.entryFileNames', () => {
4 | it ('should default to [name].js', async () => {
5 | fs.stub('./src/main.js', () => 'export default 123');
6 |
7 | let bundle = await nollup({
8 | input: './src/main.js'
9 | });
10 |
11 | let { output } = await bundle.generate({
12 | format: 'esm'
13 | });
14 |
15 | expect(output[0].fileName).to.equal('main.js');
16 | fs.reset();
17 | });
18 |
19 | it ('should allow static characters in name', async () => {
20 | fs.stub('./src/main.js', () => 'export default 123');
21 |
22 | let bundle = await nollup({
23 | input: './src/main.js'
24 | });
25 |
26 | let { output } = await bundle.generate({
27 | entryFileNames: 'entry-[name].js',
28 | format: 'esm'
29 | });
30 |
31 | expect(output[0].fileName).to.equal('entry-main.js');
32 | fs.reset();
33 | });
34 |
35 | it ('should be allowed in subdirectories', async () => {
36 | fs.stub('./src/main.js', () => 'export default 123');
37 |
38 | let bundle = await nollup({
39 | input: './src/main.js'
40 | });
41 |
42 | let { output } = await bundle.generate({
43 | entryFileNames: 'entries/entry-[name].js',
44 | format: 'esm'
45 | });
46 |
47 | expect(output[0].fileName).to.equal('entries/entry-main.js');
48 | fs.reset();
49 | });
50 |
51 | it ('should allow [format]', async () => {
52 | fs.stub('./src/main.js', () => 'export default 123');
53 |
54 | let bundle = await nollup({
55 | input: './src/main.js'
56 | });
57 |
58 | let { output } = await bundle.generate({
59 | entryFileNames: '[name].[format].js',
60 | format: 'esm'
61 | });
62 |
63 | expect(output[0].fileName).to.equal('main.esm.js');
64 | fs.reset();
65 | });
66 |
67 | it ('should allow [hash]', async () => {
68 | fs.stub('./src/main.js', () => 'export default 123');
69 |
70 | let bundle = await nollup({
71 | input: './src/main.js'
72 | });
73 |
74 | let { output } = await bundle.generate({
75 | entryFileNames: '[name].[hash].js',
76 | format: 'esm'
77 | });
78 |
79 | expect(output[0].fileName).to.equal('main.[hash].js');
80 | fs.reset();
81 | })
82 | });
--------------------------------------------------------------------------------
/test/cases/options/output-file.js:
--------------------------------------------------------------------------------
1 | let { nollup, fs, rollup, expect } = require('../../nollup');
2 |
3 | describe ('Options: output.file', () => {
4 | it ('should allow output.file to be defined', async () => {
5 | fs.stub('./src/main.js', () => 'export default 123');
6 |
7 | let bundle = await nollup({
8 | input: './src/main.js'
9 | });
10 |
11 | let { output } = await bundle.generate({
12 | file: 'output.js',
13 | format: 'esm'
14 | });
15 |
16 | expect(output[0].fileName).to.equal('output.js');
17 | expect(output[0].isEntry).to.be.true;
18 |
19 | fs.reset();
20 | });
21 |
22 | it ('should strip out directory from file as per Rollup API', async () => {
23 | fs.stub('./src/main.js', () => 'export default 123');
24 |
25 | let bundle = await nollup({
26 | input: './src/main.js'
27 | });
28 |
29 | let { output } = await bundle.generate({
30 | file: 'dist/output.js',
31 | format: 'esm'
32 | });
33 |
34 | expect(output[0].fileName).to.equal('output.js');
35 | expect(output[0].isEntry).to.be.true;
36 |
37 | fs.reset();
38 | });
39 |
40 |
41 | it ('should throw error if multiple entries are defined');
42 | });
--------------------------------------------------------------------------------
/test/nollup.js:
--------------------------------------------------------------------------------
1 | let proxyquire = require('proxyquire').noCallThru();
2 | let path = require('path');
3 | let fs_impl = require('fs');
4 | let expect = require('chai').expect;
5 | let SourceMapFast = require('source-map-fast');
6 |
7 | // Source map lib has a check for browser
8 | if (!global.window) {
9 | global.window = {};
10 | }
11 |
12 | window.fetch = undefined;
13 |
14 | let fs = {
15 | '@global': true,
16 | _stubs: {},
17 |
18 | lstatSync: function (file) {
19 | return {
20 | isSymbolicLink: () => false,
21 | isFile: () => true
22 | }
23 | },
24 |
25 | readdirSync: function (dir) {
26 | let output = [];
27 |
28 | // if (fs.existsSync(dir)) {
29 | // output = output.concat(fs.readdirSync(dir));
30 | // }
31 |
32 | Object.keys(this._stubs).forEach(file => {
33 | if (path.dirname(file) === dir) {
34 | output.push(path.basename(file));
35 | }
36 | });
37 |
38 | return output;
39 | },
40 |
41 | readFile: function (file, encoding, callback) {
42 | try {
43 | let output = this.readFileSync(file, encoding);
44 | callback(null, output);
45 | } catch (e) {
46 | callback(e);
47 | }
48 | },
49 |
50 | readFileSync: function (file, encoding) {
51 | if (this._stubs[file]) {
52 | return this._stubs[file]();
53 | }
54 |
55 | return fs_impl.readFileSync(file, encoding);
56 | },
57 |
58 | existsSync: function(file) {
59 | return Boolean(this._stubs[file]) || fs_impl.existsSync(file);
60 | },
61 |
62 | reset: function () {
63 | this._stubs = {};
64 | },
65 |
66 | stub: function (file, callback) {
67 | let fullPath = path.resolve(process.cwd(), file);
68 | this._stubs[fullPath] = callback;
69 | },
70 |
71 | promises: {
72 | lstat: async function (file) {
73 | return {
74 | isSymbolicLink: () => false,
75 | isFile: () => true
76 | }
77 | },
78 |
79 | realpath: async function (file) {
80 | },
81 |
82 | readdir: async function (directory) {
83 | return fs.readdirSync(directory);
84 | },
85 |
86 | readFile: async function (file, encoding) {
87 | return fs.readFileSync(file, encoding);
88 | }
89 | }
90 | }
91 |
92 | let nollup = proxyquire('../lib/index', { fs });
93 |
94 | let rollup = async (input) => await proxyquire('rollup', { fs }).rollup(input);
95 |
96 | module.exports = {
97 | nollup, fs, expect, rollup
98 | };
--------------------------------------------------------------------------------
/test/packages/circular-deep/A.js:
--------------------------------------------------------------------------------
1 | import B, { b_fn } from './B.js';
2 |
3 | export var impl = b_fn();
4 |
5 | export default B;
--------------------------------------------------------------------------------
/test/packages/circular-deep/B.js:
--------------------------------------------------------------------------------
1 | import C, { c_fn } from './C.js';
2 |
3 | export var b_fn = function () {
4 | return c_fn();
5 | }
6 |
7 | export default C;
--------------------------------------------------------------------------------
/test/packages/circular-deep/C.js:
--------------------------------------------------------------------------------
1 | import { impl } from './A.js';
2 |
3 | export var c_fn = function () {
4 | return 'hello world';
5 | }
6 |
7 | export default function () {
8 | return impl;
9 | };
--------------------------------------------------------------------------------
/test/packages/circular-deep/index.js:
--------------------------------------------------------------------------------
1 | import message from './message';
2 |
3 | export default message();
4 |
--------------------------------------------------------------------------------
/test/packages/circular-deep/message.js:
--------------------------------------------------------------------------------
1 | import A from './A.js';
2 |
3 | export default A;
--------------------------------------------------------------------------------
/test/packages/circular-export-all-from/A.js:
--------------------------------------------------------------------------------
1 | import { getLetter } from './letters';
2 |
3 | export function A () {
4 | return getLetter('A');
5 | }
--------------------------------------------------------------------------------
/test/packages/circular-export-all-from/index.js:
--------------------------------------------------------------------------------
1 | import { A } from './letters';
2 |
3 | export default A();
--------------------------------------------------------------------------------
/test/packages/circular-export-all-from/letters.js:
--------------------------------------------------------------------------------
1 | export function getLetter(input) {
2 | return input;
3 | }
4 |
5 | export * from './A';
--------------------------------------------------------------------------------
/test/packages/circular-export-fn-as/index.js:
--------------------------------------------------------------------------------
1 | import { impl } from './other';
2 |
3 | function hello () {
4 | return impl;
5 | }
6 |
7 | export { hello as world };
--------------------------------------------------------------------------------
/test/packages/circular-export-fn-as/other.js:
--------------------------------------------------------------------------------
1 | import { world } from './index';
2 |
3 | export const impl = 'hello';
4 |
5 | console.log(world());
--------------------------------------------------------------------------------
/test/packages/circular-export-from-infinite-loop/A.js:
--------------------------------------------------------------------------------
1 | import { getLetter } from './letters';
2 |
3 | export default function () {
4 | return getLetter('A');
5 | }
6 |
7 | export * from './letters';
--------------------------------------------------------------------------------
/test/packages/circular-export-from-infinite-loop/index.js:
--------------------------------------------------------------------------------
1 | import { A } from './letters';
2 |
3 | export default A();
--------------------------------------------------------------------------------
/test/packages/circular-export-from-infinite-loop/letters.js:
--------------------------------------------------------------------------------
1 | export function getLetter(input) {
2 | return input;
3 | }
4 |
5 | export { default as A } from './A';
--------------------------------------------------------------------------------
/test/packages/circular-export-from/A.js:
--------------------------------------------------------------------------------
1 | import { getLetter } from './letters';
2 |
3 | export default function () {
4 | return getLetter('A');
5 | }
--------------------------------------------------------------------------------
/test/packages/circular-export-from/index.js:
--------------------------------------------------------------------------------
1 | import { A } from './letters';
2 |
3 | export default A();
--------------------------------------------------------------------------------
/test/packages/circular-export-from/letters.js:
--------------------------------------------------------------------------------
1 | export function getLetter(input) {
2 | return input;
3 | }
4 |
5 | export { default as A } from './A';
--------------------------------------------------------------------------------
/test/packages/circular-hoist-class/index.js:
--------------------------------------------------------------------------------
1 | import { impl, Hello } from './other.js';
2 |
3 | var Base = class {};
4 |
5 | class HelloImplImpl extends Base {
6 | getMessage () {
7 | return impl;
8 | }
9 | }
10 |
11 | function HelloImpl () {
12 | return new HelloImplImpl().getMessage();
13 | }
14 |
15 | export { HelloImpl };
16 |
17 | console.log(Hello())
--------------------------------------------------------------------------------
/test/packages/circular-hoist-class/other.js:
--------------------------------------------------------------------------------
1 | import { HelloImpl } from './index.js';
2 |
3 | export const impl = 'hello';
4 |
5 | export function Hello () {
6 | return HelloImpl();
7 | }
--------------------------------------------------------------------------------
/test/packages/circular-hoist-fn-require/dynamic.js:
--------------------------------------------------------------------------------
1 | export default 'dynamic';
--------------------------------------------------------------------------------
/test/packages/circular-hoist-fn-require/index.js:
--------------------------------------------------------------------------------
1 | import { impl } from './other';
2 |
3 | async function hello () {
4 | let dynamic = await import('./dynamic');
5 | console.log(impl + '-' + dynamic.default);
6 | }
7 |
8 | export { hello };
--------------------------------------------------------------------------------
/test/packages/circular-hoist-fn-require/other.js:
--------------------------------------------------------------------------------
1 | import { hello } from './index';
2 |
3 | export const impl = 'hello';
4 |
5 | hello();
--------------------------------------------------------------------------------
/test/packages/circular-hoist-var-patterns-extra/index.js:
--------------------------------------------------------------------------------
1 | import { getMessage } from './other.js';
2 |
3 | function empty(){}const{a}={a:'hello'},{b}={b:'world'}
4 |
5 | const{c}=({},{
6 | c:'foo'
7 | });let[
8 | d
9 | ]=['bar'];({});function test(){}
10 |
11 | class Test {}
12 |
13 | var e = 'lorem';
14 |
15 | export const message = a + '-' + b + '-' + c + '-' + d + '-' + e;
16 |
17 | console.log(getMessage())
--------------------------------------------------------------------------------
/test/packages/circular-hoist-var-patterns-extra/other.js:
--------------------------------------------------------------------------------
1 | import { message } from './index.js';
2 |
3 | export function getMessage () {
4 | return message;
5 | }
--------------------------------------------------------------------------------
/test/packages/circular-hoist-var-patterns/index.js:
--------------------------------------------------------------------------------
1 | import { getMessage } from './other.js';
2 |
3 | var { hello: hello_alias } = { hello: 'world' };
4 | var [ , foo ] = [ 'blank', 'bar' ];
5 |
6 | var { nested: { lorem: lorem_alias }} = { nested: { lorem: 'ipsum' } }
7 |
8 | var multiA, multiB = 'multi';
9 |
10 | export const message = hello_alias + '-' + foo + '-' + lorem_alias + '-' + multiB;
11 |
12 | console.log(getMessage())
--------------------------------------------------------------------------------
/test/packages/circular-hoist-var-patterns/other.js:
--------------------------------------------------------------------------------
1 | import { message } from './index.js';
2 |
3 | export function getMessage () {
4 | return message;
5 | }
--------------------------------------------------------------------------------
/test/packages/circular-shared-import-timing/index.js:
--------------------------------------------------------------------------------
1 | import two from './two';
2 | import { shared } from './shared';
3 |
4 | console.log('log above and log below');
5 | shared();
6 |
7 | export { shared };
--------------------------------------------------------------------------------
/test/packages/circular-shared-import-timing/shared.js:
--------------------------------------------------------------------------------
1 | export function shared () {
2 | console.log('shared import');
3 | }
--------------------------------------------------------------------------------
/test/packages/circular-shared-import-timing/two.js:
--------------------------------------------------------------------------------
1 | import { shared } from './index.js';
2 |
3 | shared();
4 |
5 | export default function (){}
--------------------------------------------------------------------------------
/test/packages/circular/A1.js:
--------------------------------------------------------------------------------
1 | import A2 from './A2.js';
2 | import B from './B.js';
3 |
4 | export function create_wrapper_print (message) {
5 | return () => message + ' - A1';
6 | }
7 |
8 | B(); // should fail if circular not implemented correctly.
9 |
10 | export default () => {
11 | return A2();
12 | }
--------------------------------------------------------------------------------
/test/packages/circular/A2.js:
--------------------------------------------------------------------------------
1 | import A3 from './A3.js';
2 |
3 | export default () => {
4 | return 'A2 - ' + A3();
5 | }
--------------------------------------------------------------------------------
/test/packages/circular/A3.js:
--------------------------------------------------------------------------------
1 | import { create_wrapper_print } from './A1.js';
2 |
3 | export default create_wrapper_print('A3');
--------------------------------------------------------------------------------
/test/packages/circular/B.js:
--------------------------------------------------------------------------------
1 | export default function () {
2 | return 'B';
3 | }
--------------------------------------------------------------------------------
/test/packages/circular/index.js:
--------------------------------------------------------------------------------
1 | import A1 from './A1';
2 |
3 | export default A1();
--------------------------------------------------------------------------------
/test/packages/empty-source-mapping/content.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "hello"
3 | }
--------------------------------------------------------------------------------
/test/packages/empty-source-mapping/index.js:
--------------------------------------------------------------------------------
1 | import content from './content.json';
2 |
3 | export default content;
--------------------------------------------------------------------------------
/test/packages/export-all/impl.js:
--------------------------------------------------------------------------------
1 | export let message1 = 'hello';
2 | export let message2 = 'world';
3 | export default 123;
--------------------------------------------------------------------------------
/test/packages/export-all/index-proxy.js:
--------------------------------------------------------------------------------
1 | export * from './impl';
--------------------------------------------------------------------------------
/test/packages/export-all/index.js:
--------------------------------------------------------------------------------
1 | export { message1, message2, default } from './index-proxy';
--------------------------------------------------------------------------------
/test/packages/export-checks/alias-dep-from.js:
--------------------------------------------------------------------------------
1 | export var AliasDepFrom = 'alias-dep-from';
--------------------------------------------------------------------------------
/test/packages/export-checks/dep-from.js:
--------------------------------------------------------------------------------
1 | export var DepFrom = 'dep-from';
2 |
3 | export default 'default-dep-from';
--------------------------------------------------------------------------------
/test/packages/export-checks/index.js:
--------------------------------------------------------------------------------
1 | export var MyVar = 'MyVar';
2 |
3 | export class MyClass {
4 | getValue () {
5 | return 'MyClass';
6 | }
7 | }
8 |
9 | export { MyVar as MyVarAlias, MyClass as MyClassAlias };
10 |
11 | export { DepFrom } from './dep-from';
12 |
13 | export { default as DefaultDepFrom } from './dep-from';
14 |
15 | export { AliasDepFrom as AliasDepFromProxy } from './alias-dep-from';
16 |
17 | export default MyVar + MyVar + MyVar;
--------------------------------------------------------------------------------
/test/packages/export-declaration-late-binding/index.js:
--------------------------------------------------------------------------------
1 | import { messages } from './messages';
2 |
3 | export default messages.greeting;
--------------------------------------------------------------------------------
/test/packages/export-declaration-late-binding/messages.js:
--------------------------------------------------------------------------------
1 | export var messages;
2 |
3 | (function (messages) {
4 | messages['greeting'] = 'hello world';
5 | })(messages || (messages = {}));
--------------------------------------------------------------------------------
/test/packages/export-default-from/impl.js:
--------------------------------------------------------------------------------
1 | export default 123;
--------------------------------------------------------------------------------
/test/packages/export-default-from/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './impl';
--------------------------------------------------------------------------------
/test/packages/export-full-live-bindings/counter.js:
--------------------------------------------------------------------------------
1 | export var counter = 0;
2 |
3 | export function increment () {
4 | counter++;
5 | }
--------------------------------------------------------------------------------
/test/packages/export-full-live-bindings/index.js:
--------------------------------------------------------------------------------
1 | import { counter, increment } from './counter';
2 |
3 | increment();
4 | increment();
5 | increment();
6 |
7 | export default 'Counter: ' + counter;
--------------------------------------------------------------------------------
/test/packages/export-import-delayed/impl.js:
--------------------------------------------------------------------------------
1 | export let message = 'hello';
--------------------------------------------------------------------------------
/test/packages/export-import-delayed/index.js:
--------------------------------------------------------------------------------
1 | import { message } from './impl';
2 |
3 | export { message };
--------------------------------------------------------------------------------
/test/packages/export-same-export-as-from/hello.js:
--------------------------------------------------------------------------------
1 | export default 'hello';
--------------------------------------------------------------------------------
/test/packages/export-same-export-as-from/index.js:
--------------------------------------------------------------------------------
1 | export { default as hello } from './hello';
2 | export { default as world } from './world';
--------------------------------------------------------------------------------
/test/packages/export-same-export-as-from/world.js:
--------------------------------------------------------------------------------
1 | export default 'world';
--------------------------------------------------------------------------------
/test/packages/export-synthetic-all-from/impl.js:
--------------------------------------------------------------------------------
1 | export default {
2 | message1: 'hello',
3 | message2: 'world'
4 | };
--------------------------------------------------------------------------------
/test/packages/export-synthetic-all-from/index.js:
--------------------------------------------------------------------------------
1 | export * from './impl';
--------------------------------------------------------------------------------
/test/packages/hello-world/index.js:
--------------------------------------------------------------------------------
1 | export default 'hello world';
--------------------------------------------------------------------------------
/test/packages/multi-module/index.js:
--------------------------------------------------------------------------------
1 | import message from './message/index';
2 | import sum from './sum/index';
3 |
4 | export default {
5 | message,
6 | sum
7 | }
--------------------------------------------------------------------------------
/test/packages/multi-module/message/hello.js:
--------------------------------------------------------------------------------
1 | export default 'hello';
--------------------------------------------------------------------------------
/test/packages/multi-module/message/index.js:
--------------------------------------------------------------------------------
1 | import hello from './hello';
2 | import world from './world';
3 |
4 | export default hello + ' ' + world;
--------------------------------------------------------------------------------
/test/packages/multi-module/message/world.js:
--------------------------------------------------------------------------------
1 | export default 'world'
--------------------------------------------------------------------------------
/test/packages/multi-module/sum/index.js:
--------------------------------------------------------------------------------
1 | import one from './one';
2 | import two from './two';
3 | import three from './three';
4 |
5 | export default one + two + three;
--------------------------------------------------------------------------------
/test/packages/multi-module/sum/one.js:
--------------------------------------------------------------------------------
1 | export default 1;
--------------------------------------------------------------------------------
/test/packages/multi-module/sum/three.js:
--------------------------------------------------------------------------------
1 | export default 3;
--------------------------------------------------------------------------------
/test/packages/multi-module/sum/two.js:
--------------------------------------------------------------------------------
1 | export default 2;
--------------------------------------------------------------------------------
/test/utils/evaluator.js:
--------------------------------------------------------------------------------
1 | let { spawn } = require('child_process');
2 | let wait = require('./wait');
3 |
4 | // Stores logs generated by the test cases
5 | let logbuffer = [];
6 |
7 | /**
8 | * Returns a list of logs.
9 | * Waits until the number of requested logs have accumulated.
10 | * There's a timeout to force it to stop checking.
11 | *
12 | * @param {Number} count
13 | * @param {Number} timeout
14 | * @returns {Promise}
15 | */
16 | async function logs (count = 1, timeout = 5000) {
17 | let start = Date.now();
18 |
19 | // Wait until we acquire the requested number of logs
20 | while (logbuffer.length < count) {
21 | await wait(100);
22 |
23 | if (Date.now() - start > timeout) {
24 | break;
25 | }
26 | }
27 |
28 | // return the logs and clear it afterwards
29 | return logbuffer.splice(0, logbuffer.length);
30 | }
31 |
32 | /**
33 | * Call the global function in the VM.
34 | *
35 | * @param {String} fn
36 | * @param {*} arg
37 | */
38 | async function call (fn, arg) {
39 | // TODO: Find deterministic way of handling this
40 | await wait(100);
41 | global._evaluatorInstance.send({ call: [fn, arg] });
42 | await wait(100);
43 | }
44 |
45 | /**
46 | * Sends updated bundle chunks to VM.
47 | *
48 | * @param {Object[]} chunks
49 | */
50 | function invalidate (chunks) {
51 | global._evaluatorInstance.send({ invalidate: true, chunks });
52 | }
53 |
54 | /**
55 | * Evaluates the VM with the provided code.
56 | *
57 | * @param {String} format
58 | * @param {String} entry
59 | * @param {Object[]} chunks
60 | * @param {Object} globals
61 | * @param {Boolean} async
62 | * @returns {Object}
63 | */
64 | function init (format, entry, chunks, globals = {}, async = false) {
65 | logbuffer = [];
66 |
67 | return new Promise((resolve, reject) => {
68 | let impl = (resolve, reject) => {
69 | global._evaluatorResultListener = msg => {
70 | if (msg.log) {
71 | logbuffer.push(msg.log);
72 | return;
73 | }
74 |
75 | if (msg.error) {
76 | return reject(msg.error);
77 | }
78 |
79 | resolve({ globals: msg.globals, exports: msg.result });
80 | };
81 |
82 | global._evaluatorInstance.send({ format, entry, chunks, globals, async });
83 | };
84 |
85 | // The forked node may not be ready yet when the test starts.
86 | // This will push the test into a backlog.
87 | if (!global._evaluatorReady) {
88 | global._evaluatorReadyListeners.push(() => impl(resolve, reject));
89 | } else {
90 | impl(resolve, reject);
91 | }
92 | });
93 | }
94 |
95 | before(() => {
96 | // Create instance of NodeJS with ESM mode enabled.
97 | // We will use this one instance for all tests.
98 | // When it is ready, it will notify us and we can proceed with testing.
99 | if (!global._evaluatorInstance) {
100 | global._evaluatorReady = false;
101 | global._evaluatorReadyListeners = [];
102 | global._evaluatorResultListener = undefined;
103 |
104 | let forked = spawn('node', [
105 | '--experimental-vm-modules',
106 | process.cwd() + '/test/utils/evaluator-worker.js'
107 | ], {
108 | stdio: [null, null, null, 'ipc']
109 | });
110 |
111 | forked.stdout.on('data', d => console.log(d.toString()));
112 | forked.stderr.on('data', d => console.error(d.toString()));
113 |
114 | forked.on('message', msg => {
115 | if (msg.ready) {
116 | global._evaluatorReady = true;
117 | global._evaluatorReadyListeners.forEach(listener => listener());
118 | global._evaluatorReadyListeners = [];
119 | } else {
120 | global._evaluatorResultListener(msg);
121 | }
122 | });
123 |
124 | global._evaluatorInstance = forked;
125 | }
126 |
127 | });
128 |
129 | after(() => {
130 | if (global._evaluatorInstance) {
131 | global._evaluatorInstance.kill();
132 | }
133 |
134 | global._evaluatorInstance = undefined;
135 | });
136 |
137 | module.exports = { init, invalidate, logs, call };
138 |
--------------------------------------------------------------------------------
/test/utils/wait.js:
--------------------------------------------------------------------------------
1 | module.exports = function wait (delay) {
2 | return new Promise(resolve => setTimeout(resolve, delay));
3 | }
--------------------------------------------------------------------------------