Background:

Our cms is build up as a multiple pages application.
For the frontend our project using webpack multi entries&multi outputs.eg.

1
2
3
4
5
const entryConfig = {
header: "./app_fe/header/header.tsx",
left_menu: "./app_fe/left_menu/left_menu.tsx",
login: "./app_fe/login/login.tsx",
};

For the backend we using egg.js view rendering.eg.

1
2
3
4
nunjucks: {
enable: true,
package: 'egg-view-nunjucks',
},
1
2
3
4
5
6
config.view = {
defaultViewEngine: 'nunjucks',
mapping: {
'.tpl': 'nunjucks',
},
};
1
await ctx.render('notfound.tpl', pageOpt);

It’s almost a perfect and a stable structure but you know,we do have some problems.

  1. Everytime we make a new page,we need to make a new tpl files.
  2. Everytime we make a new page,we need to push a new entry into the entryConfig.
  3. we need to manipulate the tpl file every time we make some changes to clear the cache of the js libray.

Accomplishment:

So here we gonna resolve the problems metioned above.

Extract the base tpl files

The first thing is to seperate the business tpl files from base tpl files.why?As we wanna the business tpl files to be generated automatically,we don’t wanna push these files to the git storage.All we need to care are the base tpl files.
Our base tpl files including:
1.base.tpl (for importing the base js library)
2.header.tpl and left_menu.tpl (for the basic layout)
3.login.tpl
4.user_data.tpl (it’s gonna be replaced by cookies sooner or later)
5.template.tpl (template for generating other business tpl files),here’s the structure of it

1
2
3
4
5
6
7
8
9
{% extends "base.tpl" %}

{% block body %}
{% include "user_data.tpl" %}
{% include "header.tpl" %}
{% include "left_menu.tpl" %}
<span id="nemo-wrapper"></span>
<%= script %>
{% endblock %}

We put all the base tpl files in the templates fold in the root directory.

Why dont’ we put’em under the like app/baseView files and import it in a relative path? like below

1
{% extends "baseView/base.tpl" %}

check here you’ll get the answer,cause Nunjuck can even not support relative path~~~~

Generating the business tpl files

here we use HtmlWebpackPlugin to generate the file,basing on the template.tpl we create files according to the key of entryConfig So here we got all our business files.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (const key of Object.keys(entryConfig)) {
//业务tpl
if (!templates.includes(key)) {
array.push(
new HtmlWebpackPlugin({
filename: `../../../view/${key}.tpl`,
templateParameters: {
script: getScripts(key)
},
template: "./templates/template.tpl",
chunks: []
})
);
}
}

What if in some business files we need to import some extra js library?
we define a extraScriptMap here,like the code show below,app_user need import firebasejs,so we can concat it by the getScripts function.

1
2
3
4
5
6
7
8
9
10
11
12
13
const extraScriptMap = new Map().set("app_user", [
`<script src="https://www.gstatic.com/firebasejs/5.5.2/firebase-app.js"></script>`,
`<script src="https://www.gstatic.com/firebasejs/5.5.2/firebase-auth.js"></script>`
]);

//获取页面的完整script
const getScripts = key => {
let script = `<script src="/public/webpack/js/${key}.js"></script>`;
if (extraScriptMap.has(key)) {
script += extraScriptMap.get(key).join("");
}
return script;
};

besides the business file we also need to insert the base tpl files into the app/view folder.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const templates = [];
glob.sync("templates/*.tpl").forEach(filePath => {
const templateName = filePath.split("/")[1].split(".")[0];
templates.push(templateName);
});
//非业务tpl(header,left_menu,user_data,login)
for (const key of templates) {
//去掉template.tpl
if (Object.is(key, "template")) continue;
array.push(
new HtmlWebpackPlugin({
filename: `../../../view/${key}.tpl`,
template: `./templates/${key}.tpl`,
chunks: []
})
);
}

using glob we can find out all the files under templates folder.
As same as the buiness part , we push it into a HtmlWebpackPlugin and finally they are all insert into the app/view folder.

1
2
3
4
5
6
7
8
9
10
11
const htmlPluginArray = getHtmlPluginArray(entryConfig);
plugins: [
new ForkTsCheckerWebpackPlugin({
async: false,
tsconfig: "./tsconfig.json"
// tslint:'./tslint.json',
}),
// Ignore all locale files of moment.js
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
...htmlPluginArray
],

Till here , we solve the problem 1. For the problem 3 we just need to add a version or timestamp params after the script path.

Stuff still need to figure out:

  1. entryConfig should be generate automatically,as same as the solution of tpl files,we just use glob to fetch’em out.
  2. userData tpl for carrying use data is not reasonable,will replace it with cookies.
  3. header tpl and left menu should not be refresh every single time,for user we wanna see something instantly,so I’m thinking using puppeteer for ssr caching.