Skip to content
This repository has been archived by the owner on Feb 18, 2024. It is now read-only.

Support per-page HTML template customisation via 'mains' #1029

Merged
merged 2 commits into from
Aug 21, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ console.log(api.options.source); // /project/lib

```js
api.options.mains.index = 'app.js';
console.log(api.options.mains.index); // /project/src/app.js
console.log(api.options.mains.index); // { entry: '/project/src/app.js' }
api.options.source = 'lib';
console.log(api.options.mains.index); // /project/lib/app.js
console.log(api.options.mains.index); // { entry: /project/lib/app.js }
```

### `options.root`
Expand Down Expand Up @@ -138,27 +138,34 @@ Neutrino({
### `options.mains`

Set the main entry points for the application. If the option is not set, Neutrino defaults it to:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a lot of pre-existing duplication between api.md, creating-presets.md and customization.md that is out of scope for this PR, but something we should figure out how to avoid at some point.


```js
{
index: 'index'
}
```

Notice the entry point has no extension; the extension is resolved by webpack. If relative paths are specified,
they will be computed and resolved relative to `options.source`; absolute paths will be used as-is.

Multiple entry points and any page-specific configuration (if supported by the preset) can be specified like so:

```js
Neutrino({
mains: {
// If not specified, defaults to options.source + index.*
index: 'index',

// Override to relative, resolves to options.source + entry.*
index: 'entry',

// Override to absolute path
index: '/code/website/src/entry.js'
// Relative path, so resolves to options.source + home.*
index: 'home',

// Absolute path, used as-is.
login: '/code/website/src/login.js',

// Long form that allows passing page-specific configuration
// (such as html-webpack-plugin options in the case of @neutrinojs/web).
admin: {
entry: 'admin',
// any page-specific options here (see preset docs)
// ...
}
}
})
```
Expand Down
33 changes: 20 additions & 13 deletions docs/creating-presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,35 +272,42 @@ Set the main entry points for the application. If the option is not set, Neutrin
index: 'index'
}
```

Notice the entry point has no extension; the extension is resolved by webpack. If relative paths are specified,
they will be computed and resolved relative to `options.source`; absolute paths will be used as-is.

By default these main files are not required to be in JavaScript format. They may also potentially be JSX, TypeScript,
or any other preprocessor language. These extensions should be specified in middleware at
`neutrino.config.resolve.extensions`.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there 2 blank lines here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes I'll remove this additional newline

```js
module.exports = neutrino => {
// if not specified, defaults to an object with a single entry "index",
// resolved to options.source + index
neutrino.options.mains.index;
};
```

Multiple entry points and any page-specific configuration (if supported by the preset) can be specified like so:

```js
module.exports = {
options: {
mains: {
// If not specified, defaults to options.source + index
index: 'index',

// Override to relative, resolves to options.source + entry.*
index: 'entry',

// Override to absolute path
index: '/code/website/src/entry.js',

// Add additional main, resolves to options.source + admin.*
admin: 'admin'
// Relative path, so resolves to options.source + home.*
index: 'home',

// Absolute path, used as-is.
login: '/code/website/src/login.js',

// Long form that allows passing page-specific configuration
// (such as html-webpack-plugin options in the case of @neutrinojs/web).
admin: {
entry: 'admin',
// any page-specific options here (see preset docs)
// ...
}
}
}
};
Expand Down
26 changes: 15 additions & 11 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,21 +122,25 @@ Set the main entry points for the application. If the option is not set, Neutrin
Notice the entry point has no extension; the extension is resolved by webpack. If relative paths are specified,
they will be computed and resolved relative to `options.source`; absolute paths will be used as-is.

Multiple entry points and any page-specific configuration (if supported by the preset) can be specified like so:

```js
module.exports = {
options: {
mains: {
// If not specified, defaults to options.source + index
index: 'index',

// Override to relative, resolves to options.source + entry.*
index: 'entry',

// Override to absolute path
index: '/code/website/src/entry.js',

// Add additional main, resolves to options.source + admin.*
admin: 'admin'
// Relative path, so resolves to options.source + home.*
index: 'home',

// Absolute path, used as-is.
login: '/code/website/src/login.js',

// Long form that allows passing page-specific configuration
// (such as html-webpack-plugin options in the case of @neutrinojs/web).
admin: {
entry: 'admin',
// any page-specific options here (see preset docs)
// ...
}
}
}
};
Expand Down
6 changes: 3 additions & 3 deletions packages/library/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ module.exports = (neutrino, opts = {}) => {
babel: options.babel
});

Object
.keys(neutrino.options.mains)
.forEach(key => neutrino.config.entry(key).add(neutrino.options.mains[key]));
Object.entries(neutrino.options.mains).forEach(([name, config]) =>
neutrino.config.entry(name).add(config.entry)
);

neutrino.config
.when(hasSourceMap, () => neutrino.use(banner))
Expand Down
28 changes: 20 additions & 8 deletions packages/neutrino/Neutrino.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ const requireFromRoot = (moduleId, root) => {
// eslint-disable-next-line global-require, import/no-dynamic-require
return require(path);
};
// Support both a shorter string form and an object form that allows
// specifying any page-specific options supported by the preset.
const normalizeMainConfig = (config) =>
(typeof config === 'string') ? { entry: config } : config;

module.exports = class Neutrino {
constructor(options) {
Expand Down Expand Up @@ -138,38 +142,46 @@ module.exports = class Neutrino {

bindMainsOnOptions(options, optionsSource) {
Object
.keys(options.mains)
.forEach(key => {
let value = options.mains[key];
.entries(options.mains)
.forEach(([key, value]) => {
let normalizedConfig = normalizeMainConfig(value);

Reflect.defineProperty(options.mains, key, {
enumerable: true,
get() {
const source = optionsSource &&
optionsSource.source || options.source;

return normalizePath(source, value);
return {
...normalizedConfig,
// Lazily normalise the path, in case `source` changes after mains is updated.
entry: normalizePath(source, normalizedConfig.entry)
};
},
set(newValue) {
value = newValue;
normalizedConfig = normalizeMainConfig(newValue);
}
});
});

this.mainsProxy = new Proxy(options.mains, {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mainsProxy appears to be unused. Do we still need it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need the Proxy itself, but only exposed it on the Neutrino API just in case. We can certainly remove it if we don't foresee exposing it as being useful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing seemed to break when I removed it fwiw

defineProperty: (target, prop, { value }) => {
let currentValue = value;
let normalizedConfig = normalizeMainConfig(value);

return Reflect.defineProperty(target, prop, {
enumerable: true,
get() {
const source = optionsSource &&
optionsSource.source || options.source;

return normalizePath(source, currentValue);
return {
...normalizedConfig,
// Lazily normalise the path, in case `source` changes after mains is updated.
entry: normalizePath(source, normalizedConfig.entry)
};
},
set(newValue) {
currentValue = newValue;
normalizedConfig = normalizeMainConfig(newValue);
}
});
}
Expand Down
37 changes: 20 additions & 17 deletions packages/neutrino/test/api_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,44 +87,47 @@ test('throws when legacy options.node_modules is set', t => {
test('options.mains', t => {
const api = new Neutrino();

t.is(api.options.mains.index, join(process.cwd(), 'src/index'));
t.deepEqual(api.options.mains.index, { entry: join(process.cwd(), 'src/index') });
api.options.mains.index = './alpha.js';
t.is(api.options.mains.index, join(process.cwd(), 'src/alpha.js'));
t.deepEqual(api.options.mains.index, { entry: join(process.cwd(), 'src/alpha.js') });
api.options.source = 'beta';
t.is(api.options.mains.index, join(process.cwd(), 'beta/alpha.js'));
t.deepEqual(api.options.mains.index, { entry: join(process.cwd(), 'beta/alpha.js') });
api.options.root = '/gamma';
t.is(api.options.mains.index, join('/gamma', 'beta/alpha.js'));
t.deepEqual(api.options.mains.index, { entry: join('/gamma', 'beta/alpha.js') });
api.options.mains.index = '/alpha.js';
t.is(api.options.mains.index, '/alpha.js');
t.deepEqual(api.options.mains.index, { entry: '/alpha.js' });
});

test('override options.mains', t => {
const api = new Neutrino({
mains: {
alpha: 'beta',
gamma: 'delta'
gamma: {
entry: 'delta',
title: 'Gamma Page'
}
}
});

t.is(api.options.mains.alpha, join(process.cwd(), 'src/beta'));
api.options.mains.alpha = './alpha.js';
t.is(api.options.mains.alpha, join(process.cwd(), 'src/alpha.js'));
t.deepEqual(api.options.mains.alpha, { entry: join(process.cwd(), 'src/beta') });
api.options.mains.alpha = { entry: './alpha.js', minify: false };
t.deepEqual(api.options.mains.alpha, { entry: join(process.cwd(), 'src/alpha.js'), minify: false });
api.options.source = 'epsilon';
t.is(api.options.mains.alpha, join(process.cwd(), 'epsilon/alpha.js'));
t.deepEqual(api.options.mains.alpha, { entry: join(process.cwd(), 'epsilon/alpha.js'), minify: false });
api.options.root = '/zeta';
t.is(api.options.mains.alpha, join('/zeta', 'epsilon/alpha.js'));
t.deepEqual(api.options.mains.alpha, { entry: join('/zeta', 'epsilon/alpha.js'), minify: false });
api.options.mains.alpha = '/alpha.js';
t.is(api.options.mains.alpha, '/alpha.js');
t.deepEqual(api.options.mains.alpha, { entry: '/alpha.js' });

t.is(api.options.mains.gamma, join('/zeta', 'epsilon/delta'));
t.deepEqual(api.options.mains.gamma, { entry: join('/zeta', 'epsilon/delta'), title: 'Gamma Page' });
api.options.mains.gamma = './alpha.js';
t.is(api.options.mains.gamma, join('/zeta', 'epsilon/alpha.js'));
t.deepEqual(api.options.mains.gamma, { entry: join('/zeta', 'epsilon/alpha.js') });
api.options.source = 'src';
t.is(api.options.mains.gamma, join('/zeta', 'src/alpha.js'));
t.deepEqual(api.options.mains.gamma, { entry: join('/zeta', 'src/alpha.js') });
api.options.root = process.cwd();
t.is(api.options.mains.gamma, join(process.cwd(), 'src/alpha.js'));
t.deepEqual(api.options.mains.gamma, { entry: join(process.cwd(), 'src/alpha.js') });
api.options.mains.gamma = '/alpha.js';
t.is(api.options.mains.gamma, '/alpha.js');
t.deepEqual(api.options.mains.gamma, { entry: '/alpha.js' });
});

test('creates an instance of webpack-chain', t => {
Expand Down
8 changes: 4 additions & 4 deletions packages/node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ module.exports = (neutrino, opts = {}) => {
}, options.babel)
});

Object
.keys(neutrino.options.mains)
.forEach(key => neutrino.config.entry(key).add(neutrino.options.mains[key]));
Object.entries(neutrino.options.mains).forEach(([name, config]) =>
neutrino.config.entry(name).add(config.entry)
);

neutrino.config
.when(sourceMap, () => neutrino.use(banner))
Expand Down Expand Up @@ -92,7 +92,7 @@ module.exports = (neutrino, opts = {}) => {
const mainKeys = Object.keys(neutrino.options.mains);

neutrino.use(startServer, {
name: getOutputForEntry(neutrino.options.mains[mainKeys[0]])
name: getOutputForEntry(neutrino.options.mains[mainKeys[0]].entry)
});
config
.devtool('inline-sourcemap')
Expand Down
4 changes: 3 additions & 1 deletion packages/react-components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ module.exports = (neutrino, opts = {}) => {

readdirSync(components).forEach(component => {
// eslint-disable-next-line no-param-reassign
neutrino.options.mains[basename(component, extname(component))] = join(components, component);
neutrino.options.mains[basename(component, extname(component))] = {
entry: join(components, component)
};
});

const pkg = neutrino.options.packageJson || {};
Expand Down
27 changes: 21 additions & 6 deletions packages/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,17 +245,32 @@ array. You can also make these changes from the Neutrino API in custom middlewar

By default Neutrino, and therefore this preset, creates a single **main** `index` entry point to your application, and
this maps to the `index.*` file in the `src` directory. The extension is resolved by webpack. This value is provided by
`neutrino.options.mains` at `neutrino.options.mains.index`. This means that the Web preset is optimized toward the use
case of single-page applications over multi-page applications. If you wish to output multiple pages, you can detail
all your mains in your `.neutrinorc.js`.
`neutrino.options.mains` at `neutrino.options.mains.index`.

If you wish to output multiple pages, you can configure them like so:

```js
module.exports = {
options: {
mains: {
index: 'index', // outputs index.html from src/index.*
admin: 'admin', // outputs admin.html from src/admin.*
account: 'user' // outputs account.html from src/user.*
index: {
// outputs index.html from src/index.*
entry: 'index',
// Additional options are passed to html-webpack-plugin, and override
// any defaults set via the preset's `html` option.
title: 'Site Homepage',
},
admin: {
// outputs admin.html from src/admin.*
entry: 'admin',
title: 'Admin Dashboard',
},
account: {
// outputs account.html from src/user.* using a custom HTML template.
entry: 'user',
inject: true,
template: 'my-custom-template.html',
},
}
},
use: ['@neutrinojs/react']
Expand Down
Loading