Demand background
I recently received a request, Project was supposed to visit https://juejin.cn/pro/index.html, for example, now want to visit https://juejin.cn/pro/xxx/index.html for a merchant configuration, more than a layer of context XXX, The code is the same, except that the latter does a specific theme configuration based on localtion.pathName, showing different styles. So the question is how to make https://juejin.cn/pro/xxx/index.html to https://juejin.cn/pro/index.html.
Processing method
- configuration
nginx
thelocation
. - Copy the packaged folder directly to
xxx
directory - A new
webpack
Plugins that copy only packaged onesindex.html
The file toxxx
directory
Nginx way
Nginx is the fastest and easiest way to do this. You only need to configure location, forwarding or direction. However, due to the company’s Nginx change process, it was not implemented in this way.
Copy packaged folders directly to subdirectories
For example, the structure of the packaged file is as follows
.
|____favicon.ico
|____index.html
|____css
| |____app.0c521dcc.css
|____js
| |____app.32c95ffe.js
| |____chunk-vendors.f9baef7c.js
|____img
| |____logo.82b9c7a5.png
Copy the code
The fs module of Node copies the resources directly to the XXX folder after packaging
. |____favicon.ico |____index.html |____css | |____app.0c521dcc.css |____js | |____app.32c95ffe.js | | ____chunk - vendors. F9baef7c. Js | ____img | | ____logo. 82 b9c7a5. PNG * * * * * * * * * * * * * * * * * * copy start * * * * * * * * * * * * * * * * | ____xxx | |____favicon.ico | |____index.html | |____css | | |____app.0c521dcc.css | |____js | | |____app.32c95ffe.js | | | ____chunk - vendors. F9baef7c. Js | | ____img | | | ____logo. 82 b9c7a5. PNG * * * * * * * * * * * * * * * * * copy over * * * * * * * * * * * * * * * * *Copy the code
The above approach can be said to fulfill this custom requirements, adding a subdirectory.
Plug-in mode
Take a look at the packaged index.html file
<! DOCTYPEhtml>
<html lang=en>
<head>
<link rel=icon href=favicon.ico>
<title>vue</title>
<link href=css/app.0c521dcc.css rel=preload as=style>
<link href=js/app.32c95ffe.js rel=preload as=script>
<link href=js/chunk-vendors.f9baef7c.js rel=preload as=script>
<link href=css/app.0c521dcc.css rel=stylesheet>
</head>
<body>
<div id=app></div>
<script src=js/chunk-vendors.f9baef7c.js></script>
<script src=js/app.32c95ffe.js></script>
</body>
</html>
Copy the code
The previous method was to copy and paste by brute force, but the code is the same, this is not necessary, in fact, XXX directory just need to add an index. HTML file, and then modify publicPath. Change the reference path in the resource to.. /, so you can refer to the outer resources. The best way to do this is through webPack plug-ins.
Add a plugin called extra-html-plugin:
The plugin is simple: modify publicPath in link, script tags
extra-html-plugin.js
const { relative } = require('path');
const LINK_RE = /(\
]+>)/g
[\w\w]+?>;
const SCRIPT_RE = /(\<script src=")([^"]+)([^>]+>)/g;
// a serial execution utility function for a synchronization task
function pipe(. taskpool) {
return (. args) = > {
return taskpool.reduce((prev, curr) = > {
returncurr(prev(... args)); }); }; }module.exports = class ExtraHtmlPlugin {
// You need to pass in an absolute path to specify the generation path
// Instead of writing dead path '.. / '
constructor(options) {
this.outputDir = options.outputDir;
// The additional resource path for index.html
this.assetPath = ' ';
// the prefix of the index.html resource
this.publicPath = ' ';
}
apply(compiler) {
this.getRelativePath(compiler);
// Add extra index.html before the file emit
compiler.hooks.emit.tap('ExtraHtmlPlugin'.stats= > {
let indexHtmlContent = stats.assets['index.html'].source();
// Bind the pipe function, otherwise the "this" reference will be lost
const transformTask = pipe(
this.replaceLinkContent.bind(this),
this.replaceScriptContent.bind(this));const source = transformTask(indexHtmlContent);
// The stats in the document is named compliation object, which stands for the build object
The // assets property is a resource map that contains all the resource files in this compilation
// type must include the source and size methods
stats.assets[this.assetPath + '/index.html'] = {
source: () = > source,
size: () = > source.length
};
});
}
// Get the path to the resource prefix, and index.html
getRelativePath(compiler) {
const outputPath = compiler.options.output.path;
this.publicPath = relative(this.outputDir, outputPath) + '/';
this.assetPath = relative(outputPath, this.outputDir);
}
// Match the resources in the re for path replacement
replace(reg, content) {
return content.replace(reg, (_, $1, $2, $3) = > {
return $1 + this.publicPath + $2 + $3;
});
}
replaceLinkContent(content) {
return this.replace(LINK_RE, content);
}
replaceScriptContent(content) {
return this.replace(SCRIPT_RE, content); }};Copy the code
Realize the principle of
- Calculate the assetPath and publicPath from the outputDir passed in
- First get local compilation of the original index.html, through the re to replace the resource path
- Add directly to the resource object
stats.assets[this.assetPath + '/index.html']
Returns an object of the specified format
Here is a screenshot of the assets property:
use
In vue.config.js, reference the plug-in and specify the path to input
const ExtraHtmlPlugin = require('./script/extra-html-plugin');
module.exports = {
publicPath: '. '.productionSourceMap: false./ /... Ignored configurationChainWebpack (config) {config.plugin('extra-html').use(ExtraHtmlPlugin, [
{
outputDir: resolve('dist/xxx')}]); }}Copy the code
After the NPM run build, look at the package result. It is exactly the package structure we want.
<! DOCTYPEhtml>
<html lang=en>
<head>
<link rel=icon href=../favicon.ico>
<title>vue</title>
<link href=../css/app.0c521dcc.css rel=preload as=style>
<link href=../js/app.32c95ffe.js rel=preload as=script>
<link href=../js/chunk-vendors.f9baef7c.js rel=preload as=script>
<link href=../css/app.0c521dcc.css rel=stylesheet>
</head>
<body>
<div id=app></div>
<script src=../js/chunk-vendors.f9baef7c.js></script>
<script src=../js/app.32c95ffe.js></script>
</body>
</html>
Copy the code
Double-click xxx.html to open the browser error, not running, telling me that the CSS reference failed
After debugging, the Boostrap file displays a resource path reference problem
When referring to the CSS of the subcontract file, because we configured publicPath is., __webpack_require__.p is “”, we will search from the current folder XXX, but XXX directory only has one index.html, so we can’t find it. We want the chunk referenced from the top as well.. __webpack_require__.p needs to be dynamically corrected.
Dynamically modify the publicPath in WebPack
Webpack provides a series of hooks. For example, mainTemplate can adjust the generation of bootstrap function in different modes. Can we add a section of logic to the bootstrap function? At the time of the bootstrap to perform dynamic modifying __webpack_require__. P, looked at the documents found lots of preset hook can do this, here chose mainTemplate. Hooks. RequireExtensions this hook in our function.
Modify extra-html-plugin to the extra-html-plugin you wrote earlier
const { relative } = require('path');
const LINK_RE = /(\
]+>)/g
[\w\w]+?>;
const SCRIPT_RE = /(\<script src=")([^"]+)([^>]+>)/g;
// Dynamically modifies a function of publicPath
const asyncPublicPath = (r, p) = > `
function getAsyncPublicPath () {
if (window.location.pathname.indexOf('${r}') > -1) {
__webpack_require__.p = "${p}";
window.__webpack_require__ = __webpack_require__;
}
};
getAsyncPublicPath();`;
function pipe(. taskpool) {
return (. args) = > {
return taskpool.reduce((prev, curr) = > {
returncurr(prev(... args)); }); }; }module.exports = class ExtraHtmlPlugin {
constructor(options) {
this.outputDir = options.outputDir;
this.assetPath = ' ';
this.publicPath = ' ';
}
apply(compiler) {
this.getRelativePath(compiler);
compiler.hooks.emit.tap('ExtraHtmlPlugin'.stats= > {
let indexHtmlContent = stats.assets['index.html'].source();
const transformTask = pipe(this.replaceLinkContent.bind(this), this.replaceScriptContent.bind(this));
const source = transformTask(indexHtmlContent);
stats.assets[this.assetPath + '/index.html'] = {
source: () = > source,
size: () = > source.length
};
});
compiler.hooks.compilation.tap('main'.stats= > {
// Insert our function fragment in this hook
stats.mainTemplate.hooks.requireExtensions.tap('main'.(source, chunk, hash) = > {
const chunkMap = chunk.getChunkMaps();
// This fragment will only be included in the main package
if (Object.keys(chunkMap.hash).length) {
const buff = [source];
buff.push('\n\n// rewrite __webpack_public_path__');
buff.push(asyncPublicPath(this.assetPath, this.publicPath));
return buff.join('\n');
} else {
returnsource; }}); }); }getRelativePath(compiler) {
const outputPath = compiler.options.output.path;
this.publicPath = relative(this.outputDir, outputPath) + '/';
this.assetPath = relative(outputPath, this.outputDir);
}
replace(reg, content) {
return content.replace(reg, (_, $1, $2, $3) = > {
return $1 + this.publicPath + $2 + $3;
});
}
replaceLinkContent(content) {
return this.replace(LINK_RE, content);
}
replaceScriptContent(content) {
return this.replace(SCRIPT_RE, content); }};Copy the code
Rerun NPM run serve and look at the bootstrap function. Additional asyncPublicPath functions are coming in
Check the page and functional things everything is normal, there is no error, this is really OK.
The next day I checked the documentation and found that changing publicPath dynamically didn’t have to be that much trouble at all. All I had to do was add the __webpack_require__.p variable to the entry file. Webpack specifically handles the __webpack_require__ variable when doing ast parsing,
Just import our function in the entry file
main.js
function getAsyncPublicPath () {
if (window.location.pathname.indexOf('xxx') > -1) {
__webpack_require__.p = ".. /";
window.__webpack_require__ = __webpack_require__; }}; getAsyncPublicPath();new Vue({
render: h= > <App />
})
Copy the code
Since the entry module, webpack passes in a custom __webpack_require__ function called require, which is a reference object with properties that developers can modify or add in their code. Remember to reset __webpack_require__.p in the entry file.
Since this method requires less code, I also modified publicPath by resetting __webpack_require__.p in the entry file.
I have to say that Webpack’s documentation is so convoluted and hard to understand that it discourages newbies