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

Add support for code splitting and static assets #104

Merged
merged 7 commits into from Nov 1, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -61,7 +61,9 @@ mvn archetype:generate \

## Docs

- [SPA ClientLib](./docs/spa-clientlib.md)
- [Browser support](./docs/browser-support.md)
- [Code Splitting](./docs/code-splitting.md)

### Contributing

Expand Down
21 changes: 21 additions & 0 deletions docs/code-splitting.md
@@ -0,0 +1,21 @@
# Code Splitting

## Overview

The React app is configured to make use of [code splitting](https://webpack.js.org/guides/code-splitting) by default. When building the app for production, the code will be output in several chunks:
habansal marked this conversation as resolved.
Show resolved Hide resolved

```sh
$ ls build/static/js
2.5b77f553.chunk.js
2.5b77f553.chunk.js.map
main.cff1a559.chunk.js
main.cff1a559.chunk.js.map
runtime~main.a8a9905a.js
runtime~main.a8a9905a.js.map
```

Loading chunks only when they are required can improve the app performance significantly.

## Implementation

To get this feature to work with AEM, the app needs to be able to identify which JS and CSS files need to be requested from the HTML generated by AEM. This can be achieved using the `"entrypoints"` key in the `asset-manifest.json` file: The file is parsed in `clientlib.config.js` and only the entrypoint files are bundled into the ClientLib. The remaining files are placed in the ClientLib's `resources` directory and will be requested dynamically and therefore only loaded when they are actually needed.
16 changes: 16 additions & 0 deletions docs/spa-clientlib.md
@@ -0,0 +1,16 @@
# SPA ClientLib

The SPA is made available using an [AEM ClientLib](https://helpx.adobe.com/experience-manager/6-5/sites/developing/using/clientlibs.html). When executing `npm run build`, the app is built and the [`aem-clientlib-generator`](https://github.com/wcm-io-frontend/aem-clientlib-generator) package takes the resulting `build` directory and transforms it into such a ClientLib.

The ClientLib will consist of the following files and directories:

- `css/`
- CSS files which need to be requested in the HTML
- `js/`
- JavaScript files which need to be requested in the HTML
- `resources/`
- Source maps
- Non-entrypoint code chunks (see [Code Splitting](./code-splitting.md))
- Static assets (e.g. icons)
- `css.txt` (tells AEM the order and names of files in `css/` so they can be merged)
- `js.txt` (tells AEM the order and names of files in `js/` so they can be merged)
Expand Up @@ -13,32 +13,57 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
module.exports = {
// default working directory (can be changed per 'cwd' in every asset option)
context: __dirname,

// path to the clientlib root folder (output)
clientLibRoot: "./../ui.apps/src/main/content/jcr_root/apps/${projectName}/clientlibs",
const path = require('path');

const BUILD_DIR = path.join(__dirname, 'dist', 'browser');
const CLIENTLIB_DIR = path.join(
__dirname,
'..',
'ui.apps',
'src',
'main',
'content',
'jcr_root',
'apps',
'${projectName}',
'clientlibs'
);

// Globs for entrypoint files (in the order they need to be required)
const jsEntrypoints = [
'**/runtime*.js',
'**/polyfills*.js',
'**/styles*.js',
'**/vendor*.js',
'**/main*.js'
];
const cssEntrypoints = ['**/*.css'];

// Config for `aem-clientlib-generator`
module.exports = {
context: BUILD_DIR,
clientLibRoot: CLIENTLIB_DIR,
libs: {
name: '${projectName}-${optionFrontend}',
allowProxy: true,
categories: ['${projectName}-${optionFrontend}'],
embed: ['${projectName}.responsivegrid'],
jsProcessor: ['default:none', 'min:none'],
serializationFormat: 'xml',
assets: {
// Copy entrypoint scripts and stylesheets into the respective ClientLib directories (in the order they are in the
// entrypoints arrays)
js: jsEntrypoints,
css: cssEntrypoints,

libs: {
name: "${projectName}-${optionFrontend}",
allowProxy: true,
categories: ["${projectName}-${optionFrontend}"],
embed: ["${projectName}.responsivegrid"],
jsProcessor: ['default:none', 'min:none'],
serializationFormat: "xml",
assets: {
js: [
"dist/browser/**/runtime*.js",
"dist/browser/**/polyfills*.js",
"dist/browser/**/styles*.js",
"dist/browser/**/vendor*.js",
"dist/browser/**/main*.js",
"dist/browser/**/*.map"
],
css: [
"dist/browser/**/*.css"
]
}
// Copy all other files into the `resources` ClientLib directory
resources: {
cwd: '.',
flatten: false,
files: ['**/*.*'],
ignore: [...jsEntrypoints, ...cssEntrypoints]
}
}
}
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Expand Up @@ -2,6 +2,7 @@
"name": "angular-app",
"version": "1.1.0",
"private": true,
"homepage": "/etc.clientlibs/${projectName}/clientlibs/${projectName}-${optionFrontend}/resources",
"scripts": {
"start": "ng serve --open --proxy-config ./proxy.aem.conf.json",
"build": "ng build && clientlib",
Expand Down Expand Up @@ -49,7 +50,7 @@
"@types/jasmine": "~3.3.16",
"@types/jasminewd2": "^2.0.6",
"@types/node": "^12.6.8",
"aem-clientlib-generator": "^1.4.3",
"aem-clientlib-generator": "^1.5.0",
"body-parser": "^1.19.0",
"codelyzer": "^5.0.1",
"jasmine-core": "^3.4.0",
Expand Down
Expand Up @@ -13,27 +13,57 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
module.exports = {
// default working directory (can be changed per 'cwd' in every asset option)
context: __dirname,

// path to the clientlib root folder (output)
clientLibRoot: "./../ui.apps/src/main/content/jcr_root/apps/${projectName}/clientlibs",
const fs = require('fs');
const path = require('path');

const BUILD_DIR = path.join(__dirname, 'build');
const CLIENTLIB_DIR = path.join(
__dirname,
'..',
'ui.apps',
'src',
'main',
'content',
'jcr_root',
'apps',
'${projectName}',
'clientlibs'
);

// Read entrypoint files and order from `asset-manifest.json`
const assetManifest = fs.readFileSync(path.join(BUILD_DIR, 'asset-manifest.json'), {
encoding: 'utf8'
});
const { entrypoints } = JSON.parse(assetManifest);
const jsEntrypoints = entrypoints.filter(fileName => fileName.endsWith('.js'));
const cssEntrypoints = entrypoints.filter(fileName => fileName.endsWith('.css'));

// Config for `aem-clientlib-generator`
module.exports = {
context: BUILD_DIR,
clientLibRoot: CLIENTLIB_DIR,
libs: {
name: '${projectName}-${optionFrontend}',
allowProxy: true,
categories: ['${projectName}-${optionFrontend}'],
embed: ['${projectName}.responsivegrid'],
jsProcessor: ['default:none', 'min:none'],
serializationFormat: 'xml',
assets: {
// Copy entrypoint scripts and stylesheets into the respective ClientLib directories (in the order they are in the
// entrypoints arrays). They will be bundled by AEM and requested from the HTML. The remaining chunks (placed in
// `resources`) will be loaded dynamically
js: jsEntrypoints,
css: cssEntrypoints,

libs: {
name: "${projectName}-${optionFrontend}",
allowProxy: true,
categories: ["${projectName}-${optionFrontend}"],
embed: ["${projectName}.responsivegrid"],
jsProcessor: ['default:none', 'min:none'],
serializationFormat: "xml",
assets: {
js: [
"build/static/**/*.js"
],
css: [
"build/static/**/*.css"
]
}
// Copy all other files into the `resources` ClientLib directory
resources: {
cwd: '.',
flatten: false,
files: ['**/*.*'],
ignore: [...jsEntrypoints, ...cssEntrypoints]
}
}
}
};