Skip to content

Commit

Permalink
feat(stark-build): upgrade to Angular 8
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
  Support for `htmlWebpackPlugin.options` has been removed.
A new support for `starkAppMetadata`, `starkAppConfig` and `metadata` has been implemented. You can now use `starkOptions` instead of `htmlWebpackPlugin.options`.
See the following example:

  Before:

  ```html
  <%= htmlWebpackPlugin.options.starkAppMetadata.name %>
  <!-- or -->
  <%= htmlWebpackPlugin.options.starkAppConfig.defaultLanguage %>
  <!-- or -->
  <%= htmlWebpackPlugin.options.metadata.TITLE %>
  ```

  After:

  ```html
  <%= starkOptions.starkAppMetadata.name %>
  <!-- or -->
  <%= starkOptions.starkAppConfig.defaultLanguage %>
  <!-- or -->
  <%= starkOptions.metadata.TITLE %>
  ```

BREAKING CHANGE:
Adapt "angular.json" file as follows:
  ```text
  {
    //...
    "projects": {
      "<project_name>": {
        "architect": {
          "build": {
            // ...
            // /!\ Add the following line
            "indexTransform": "./node_modules/@nationalbankbelgium/stark-build/config/index-html.transform.js",
            // ...
          },
          "serve": {
            // Edit the following line.
            // Before:
            // "builder": "@angular-builders/dev-server:generic",
            // Now:
            "builder": "@angular-builders/custom-webpack:dev-server",
            // ...
          }
        }
      }
    }
  }
  ```

BREAKING CHANGE:
Adapt the "index.html" as follows:
  ```html
  <html lang="en">
    <head>
      <!-- ... -->
      <!-- Adapt the title tag as follows -->
      <!-- Before: -->
      <title><%= htmlWebpackPlugin.options.starkAppMetadata.name %></title>
      <!-- After: -->
      <title>%starkAppMetadata.name%</title>

      <!-- /!\ Remove the following lines -->
      <meta name="description" content="<%= htmlWebpackPlugin.options.starkAppMetadata.description %>" />
      <% if (webpackConfig.htmlElements.headTags) { %>
        <!--Configured Head Tags  -->
      <%= webpackConfig.htmlElements.headTags %> <% } %>
      <!-- ... -->
    </head>

    <!-- -->
  </html>
  ```

BREAKING CHANGE:
Add the "config/index-head-config.js" the "description" meta in as follows:
  ```text
  {
    links: [
      // ...
    ],
    meta: [
      // ...
      { name: "description", content: "%starkAppMetadata.description%" },
    ]
  }
  ```
  • Loading branch information
SuperITMan authored and ageorges-nbb committed Mar 22, 2021
1 parent 2bc701e commit c3c83b6
Show file tree
Hide file tree
Showing 6 changed files with 3,918 additions and 4,193 deletions.
51 changes: 16 additions & 35 deletions docs/stark-build/NG_CLI_BUILD_CUSTOMIZATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Any app using **Stark** should include the customizations provided by Stark-Buil
"replaceDuplicatePlugins": false
}
},
"indexTransform": "./node_modules/@nationalbankbelgium/stark-build/config/index-html.transform.js",
...
},
"configurations": {
Expand Down Expand Up @@ -83,7 +84,7 @@ Any app using **Stark** should include the customizations provided by Stark-Buil
}
},
"serve": {
"builder": "@angular-builders/dev-server:generic",
"builder": "@angular-builders/custom-webpack:dev-server",
"options": {
"browserTarget": "your-app:build"
},
Expand All @@ -108,7 +109,7 @@ Any app using **Stark** should include the customizations provided by Stark-Buil
Notice that the custom Webpack partial configurations are set to the dev and prod configurations in the `build` and the `serve` targets.

This must be done by using the `@angular-builders/custom-webpack:browser` builder in the `build` target and set the `customWebpackConfig` options.
For the `serve` target this must be done by using `@angular-builders/dev-server:generic` builder.
For the `serve` target this must be done by using `@angular-builders/custom-webpack:dev-server` builder.

## Webpack Customizations

Expand Down Expand Up @@ -183,30 +184,29 @@ if (ENV === "development") {
}
```

#### [HtmlWebpackPlugin](https://github.com/jantimon/html-webpack-plugin "HtmlWebpackPlugin")
#### [Html indexTransform](https://github.com/NationalBankBelgium/stark/blob/master/packages/stark-build/config/index-html.transform.js "Html indexTransform")

Simplifies the creation of the main _index.html_ file in the application bundle. This plugin will generate the final _index.html_ based on
the `src/index.html` file in your project.
This plugin extends the Angular CLI build system.

This plugin is configured to use the [ejs-loader](https://github.com/okonet/ejs-loader) for [Underscore/LoDash Templates](https://lodash.com/docs#template).
Which means that you can use that templating syntax in your `src/index.html`. This is really powerful given the information from the HtmlWebpackPlugin
that you can access in your template to customize it. For example:
This plugin is configured to use the [Underscore/LoDash Templates](https://lodash.com/docs#template).
Which means that you can use that templating syntax in your `src/index.html`. This is really powerful given the information from the Html IndexTransform
that you can access in your _index.html_ template to customize it. For example:

```html
<html lang="en">
<head>
<!-- Use the application name from StarkAppMetadata as the Page title -->
<title><%= htmlWebpackPlugin.options.starkAppMetadata.name %></title>
<title><%= starkOptions.starkAppMetadata.name %></title>
</head>
<body>
Some content here
</body>
</html>
```

This is the information from HtmlWebpackPlugin that is accessible in the template:
This is the information from `indexTransform` that is accessible in the template:

- **options:** all options that were passed to the plugin including plugin's own options as well as Stark custom data containing the following:
- **starkOptions:** all options that were passed to the plugin including plugin's own options as well as Stark custom data containing the following:
- **metadata:**
- **TITLE:** Default title for Stark based apps: "Stark Application by @NationalBankBelgium"
- **BASE_URL:** The base URL of the current build
Expand All @@ -221,11 +221,9 @@ This is the information from HtmlWebpackPlugin that is accessible in the templat
- **starkAppMetadata:** the Stark metadata of the application available in the `src/stark-app-metadata.json` file
- **starkAppConfig:** the Stark specific configuration for the application available in the `src/stark-app-config.json` file

#### [BaseHrefWebpackPlugin](https://github.com/dzonatan/base-href-webpack-plugin "BaseHrefWebpackPlugin")
The Angular CLI build system will automatically add the base tag `<base href="<custom-base-url>">` to the _index.html_ so you don't need to add it manually yourself.

Allows to customize the base url in the _index.html_ via the Webpack config.

In Stark-Build, the custom base url provided to this plugin is the one you define in the **baseHref** option of your project's `angular.json` file:
If you desire to customize it, you can define it in the **baseHref** option of your project's `angular.json` file:

```text
{
Expand All @@ -247,9 +245,7 @@ In Stark-Build, the custom base url provided to this plugin is the one you defin
}
```

This plugin will automatically add the base tag `<base href="<custom-base-url>">` to the _index.html_ so you don't need to add it manually yourself.

#### [HtmlElementWebpackPlugin](https://github.com/fulls1z3/html-elements-webpack-plugin "HtmlElementWebpackPlugin")
#### [HtmlHeadElements](https://github.com/NationalBankBelgium/stark/blob/master/packages/stark-build/config/html-head-elements "HtmlHeadElements")

This plugin appends head elements during the creation of _index.html_.

Expand Down Expand Up @@ -278,6 +274,7 @@ module.exports = {
],
meta: [
...
{ name: "description", "content": "<%= starkOptions.starkAppMetadata.description %>"}
{ name: "apple-mobile-web-app-capable", content: "yes" },
{ name: "apple-mobile-web-app-status-bar-style", content: "black" },
{ name: "apple-mobile-web-app-title", content: "template" },
Expand All @@ -287,23 +284,7 @@ module.exports = {
}
```

Finally, to include in your _index.html_ file the elements defined in this new file, you will have to add
the following lines in your `<head>` section:

<!-- prettier-ignore-start -->
```html
<head>
...
<% if (webpackConfig.htmlElements.headTags) { %>
<%= webpackConfig.htmlElements.headTags %>
<% } %>
...
</head>
```
<!-- prettier-ignore-end -->

_If you do not intend to use this feature, simply don't create the `index-head-config.js` file and
don't include the check for `webpackConfig.htmlElements.headTags` in the `<head>` section of index.html._
_If you do not intend to use this feature, simply don't create the `index-head-config.js` file._

#### [ContextReplacementPlugin](https://webpack.js.org/plugins/context-replacement-plugin/)

Expand Down
89 changes: 89 additions & 0 deletions packages/stark-build/config/html-head-elements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// This code has been copied from: https://github.com/fulls1z3/html-elements-webpack-plugin/blob/master/lib/html-elements-webpack-plugin.js

const RE_ENDS_WITH_BS = /\/$/;

/**
* Create an HTML tag with attributes from a map.
*
* Example:
* createTag('link', { rel: "manifest", href: "/assets/manifest.json" })
* // <link rel="manifest" href="/assets/manifest.json">
* @param tagName The name of the tag
* @param attrMap A Map of attribute names (keys) and their values.
* @param publicPath a path to add to eh start of static asset url
* @returns {string}
*/
function createTag(tagName, attrMap, publicPath) {
publicPath = publicPath || "";

// add trailing slash if we have a publicPath and it doesn't have one.
if (publicPath && !RE_ENDS_WITH_BS.test(publicPath)) {
publicPath += "/";
}

const attributes = Object.getOwnPropertyNames(attrMap)
.filter(function (name) {
return name[0] !== "=";
})
.map(function (name) {
var value = attrMap[name];

if (publicPath) {
// check if we have explicit instruction, use it if so (e.g: =herf: false)
// if no instruction, use public path if it's href attribute.
const usePublicPath = attrMap.hasOwnProperty("=" + name) ? !!attrMap["=" + name] : name === "href";

if (usePublicPath) {
// remove a starting trailing slash if the value has one so we wont have //
value = publicPath + (value[0] === "/" ? value.substr(1) : value);
}
}

return `${name}="${value}"`;
});

const closingTag = tagName === "script" ? "</script>" : "";

return `<${tagName} ${attributes.join(" ")}>${closingTag}`;
}

/**
* Returns a string representing all html elements defined in a data source.
*
* Example:
*
* const ds = {
* link: [
* { rel: "apple-touch-icon", sizes: "57x57", href: "/assets/icon/apple-icon-57x57.png" }
* ],
* meta: [
* { name: "msapplication-TileColor", content: "#00bcd4" }
* ]
* }
*
* getHeadTags(ds);
* // "<link rel="apple-touch-icon" sizes="57x57" href="/assets/icon/apple-icon-57x57.png">"
* "<meta name="msapplication-TileColor" content="#00bcd4">"
*
* @returns {string}
*/
function getHtmlElementString(dataSource, publicPath) {
return Object.getOwnPropertyNames(dataSource)
.map(function (name) {
if (Array.isArray(dataSource[name])) {
return dataSource[name].map(function (attrs) {
return createTag(name, attrs, publicPath);
});
} else {
return [createTag(name, dataSource[name], publicPath)];
}
})
.reduce(function (arr, curr) {
return arr.concat(curr);
}, [])
.join("\n\t");
}

module.exports = {
getHtmlElementString
};
90 changes: 90 additions & 0 deletions packages/stark-build/config/index-html.transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
const helpers = require("./helpers");
const fs = require("fs");
const commonData = require("./webpack.common-data.js"); // common configuration between environments
const HtmlHeadElements = require("./html-head-elements");
const ngCliUtils = require("./ng-cli-utils");
const buildUtils = require("./build-utils");

const isProd = ngCliUtils.getNgCliCommandOption("prod");
const isHMR =
ngCliUtils.getNgCliCommandOption("configuration") === "hmr" ||
ngCliUtils.hasNgCliCommandOption("hmr") ||
buildUtils.ANGULAR_APP_CONFIG.buildOptions.hmr;

const METADATA = Object.assign({}, buildUtils.DEFAULT_METADATA, {
ENV: isProd ? "production" : "development",
BASE_URL: ngCliUtils.getNgCliCommandOption("baseHref") || buildUtils.ANGULAR_APP_CONFIG.buildOptions.baseHref,
HMR: isHMR,
AOT: ngCliUtils.hasNgCliCommandOption("aot") || buildUtils.ANGULAR_APP_CONFIG.buildOptions.aot,
WATCH:
!(ngCliUtils.hasNgCliCommandOption("watch") && ngCliUtils.getNgCliCommandOption("watch") === "false") &&
!(buildUtils.ANGULAR_APP_CONFIG.buildOptions.watch === false), // by default is true
environment: isHMR ? "hmr" : "dev",
IS_DEV_SERVER: helpers.hasProcessFlag("serve") && !isProd // NG CLI command 'serve"
});

/**
* Check if one or more placeholders are present in the generated "index.html" and replace them
* with the associated value if exists in "starkAppMetadata", "starkAppConfig" or "metatada"
*
* The placeholder should be like this:
```html
<%= starkOptions.starkAppMetadata.name %>
<!-- or -->
<%= starkOptions.starkAppConfig.defaultLanguage %>
<!-- or -->
<%= starkOptions.metadata.TITLE %>
```
*
* @param indexHtml
* @returns {string}
*/
function replacePlaceholdersByValues(indexHtml) {
const regex = /<%=\sstarkOptions\.(starkAppMetadata|starkAppConfig|metadata)\.\w+\s%>/g;

const getRealValue = (placeholder) => {
const str = placeholder.slice(4, -3).split(".");
if (str.length === 3) {
const configName = str[1];
const property = str[2];

let value;

if (configName === "metadata") {
value = METADATA[property];
} else {
value = commonData[configName][property];
}

if (value) {
return value;
}
}

return placeholder;
};

return indexHtml.replace(regex, getRealValue);
}

module.exports = (targetOptions, indexHtml) => {
let indexHtmlToReturn = indexHtml;

/**
* Generate html tags based on javascript maps.
*
* If a publicPath is set in the webpack output configuration, it will be automatically added to
* href attributes, you can disable that by adding a "=href": false property.
* You can also enable it to other attribute by settings "=attName": true.
*
* The configuration supplied is map between a location (key) and an element definition object
*/
if (fs.existsSync(helpers.root("config/index-head-config.js"))) {
const i = indexHtml.indexOf("</head>");
indexHtmlToReturn = `${indexHtml.slice(0, i)}
${HtmlHeadElements.getHtmlElementString(require(helpers.root("config/index-head-config")), METADATA.BASE_URL)}
${indexHtml.slice(i)}`;
}

return replacePlaceholdersByValues(indexHtmlToReturn);
};

0 comments on commit c3c83b6

Please sign in to comment.