__webpack_public_path__ does not work after reloading the page - javascript

We are working on a Vue.js application which is built with webpack. The requirement is that our application not only has to be accessible via the base path / but also via a custom path, so that the application could be accessable next to other services. With webpacks \__webpack_public_path__ I was able to set the base path on the fly regarding a specified base path, like if the current path has got /foo/ in it, the base path should start at .../foo/.
While navigating through the application everything looks good, as long as I open the app at the base path. The problem is that after I navigate to any sub path and reload the page, no files can be found. The server will first provide the index.html and there I need to use relative paths, since the base path must be determined dynamically. But if any other than the base path is opened, the relative paths in the index.html won't point to the correct bundle.js and the \__webpack_public_path__ will not be set...
Currently we have got two entry points in our webpack config. The first one is a JS file where we the global variable \__webpack_public_path__ will be set relatively based on the current visited URL. So lets say the base path should start at /foo/ and the visited URL is https://www.host.com/some/path/foo/sub/sub1. Therefore the relatively base path will be /some/path/foo/ and assets will be found at i.e. /some/path/foo/assets. All other paths used in the webpack config, like in the file-loader or any other plugin, are relative.
// webpack.config.js
...
module.exports = (env, argv) => ({
...
entry: ['#babel/polyfill', './src/main/js/constants/webpack-public-path.js', './src/main/js/index.js'],
output: {
filename: 'assets/js/bundle.js',
path: path.resolve(__dirname, relativeOutputPath),
publicPath: '/'
},
module: {
...
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'file-loader',
options: {
name: 'assets/fonts/[name].[ext]'
}
}
...
},
...
});
// webpack-public-path.js
__webpack_public_path__ = (() => {
const BASE_PATH = '/foo/';
let path = window.location.pathname;
return path.includes(BASE_PATH) ? path.substring(0, path.indexOf(BASE_PATH) + BASE_PATH.length) : '/';
})();
Has anyone ever faced a similar problem? Is it possible, that the relative paths in the index.html can be defined afterwards or maybe are defined at build time by webpack?

I got a solution for my problem. What I had to do was to load all necessary assets manually in the index.html. Unfortunatly I have to maintain the determination of the dynamic base path on two different files (index.html and the webpack-public-path.js) but at least it's working now. So in the index.html I insert respective tags into the DOM manually and therefore load all needed assets based on the determined base path.

Related

How to include manual import() in Webpack Bundle

I am quite new to Webpack, so bear with me if thats a stupid question.
My goal is to transform my old, AMD based codebase to a ES6 Module based solution. What I am struggling with is handling dynamic import()s. So my app router works on a module basis, i.e. each route is mapped to a module path and then required. Since I know what modules will be included, I just add those dynamically imported modules to my r.js configuration and am able to build everything in a single file, with all require calls still working.
Now, I am trying to do the same with ES6 modules and Webpack. With my devmode this is no problem as I can just replace require() with import(). However I cannot get this to work with bundling. Either Webpack splits my code (and still fails to load the dynamic module anyways), or - if I use the Array format for the entry config, the dynamic module is included in the bundle but loading still fails: Error: Cannot find module '/src/app/DynClass.js'
This is how my Webpack config looks like:
const webpack = require('webpack');
const path = require('path');
module.exports = {
mode: "development",
entry: ['./main.js', './app/DynClass.js'],
output: {
filename: 'main.js',
path: path.resolve(__dirname, "../client/")
},
resolve: {
alias: {
"/src": path.resolve(__dirname, '')
}
},
module: {
rules: [
{
test: /\.tpl$/i,
use: 'raw-loader',
},
]
}
};
So basically I want to tell Webpack: "hey, there is another module (or more) that is to be loaded dynamically and I want it to be included in the bundle"
How can I do this?
So yeah, after much fiddling there seems to be a light at the end of the tunnel. Still, this is not a 100% solution and it is surely not for the faint of heart, as it is quite ugly and fragile. But still I want to share my approach with you:
1) manual parsing of my routes config
My router uses a config file looking like this:
import StaticClass from "/src/app/StaticClass.js";
export default {
StaticClass: {
match: /^\//,
module: StaticClass
},
DynClass: {
match: /^\//,
module: "/src/app/DynClass.js"
}
};
So as you can see the export is an object, with keys acting as the route id, and an object that contains the matches (regex based) and the module which should be executed by the router if the route matches. I can feed my router with both a Constructor function (or an object) for modules which are available immediatly (i.e. contained in the main chunk) or if the module value is a string, this means that the router has to load this module dynamically by using the path specified in the string.
So as I know what modules could be potentially loaded (but not if and when) I can now parse this file within my build process and transform the route config to something webpack can understand:
const path = require("path");
const fs = require("fs");
let routesSource = fs.readFileSync(path.resolve(__dirname, "app/routes.js"), "utf8");
routesSource = routesSource.substr(routesSource.indexOf("export default"));
routesSource = routesSource.replace(/module:\s*((?!".*").)*$/gm, "module: undefined,");
routesSource = routesSource.replace(/\r?\n|\r/g, "").replace("export default", "var routes = ");
eval(routesSource);
let dummySource = Object.entries(routes).reduce((acc, [routeName, routeConfig]) => {
if (typeof routeConfig.module === "string") {
return acc + `import(/* webpackChunkName: "${routeName}" */"${routeConfig.module}");`;
}
return acc;
}, "") + "export default ''";
(Yeah I know this is quite ugly and also a bit brittle so this surely could be done better)
Essentially I create a new, virtual module where every route entry which demands a dynamic import is translated, so:
DynClass: {
match: /^\//,
module: "/src/app/DynClass.js"
}
becomes:
import(/* webpackChunkName: "DynClass" */"/src/app/DynClass.js");
So the route id simply becomes the name of the chunk!
2) including the virtual module in the build
For this I use the virtual-module-webpack-plugin:
plugins: [
new VirtualModulePlugin({
moduleName: "./app/dummy.js",
contents: dummySource
})
],
Where dummySource is just a string containing the sourcecode of my virtual module I just have generated. Now, this module is pulled in and the "virtual imports" can be processed by webpack. But wait, I still need to import the dummy module, but I do not have any in my development mode (where I use everything natively, so no loaders).
So in my main code I do the following:
let isDev = false;
/** #remove */
isDev = true;
/** #endremove */
if (isDev) { import('./app/dummy.js'); }
Where "dummy.js" is just an empty stub module while I am in development mode. The parts between that special comments are removed while building (using the webpack-loader-clean-pragma loader), so while webpack "sees" the import for dummy.js, this code will not be executed in the build itself since then isDev evaluates to false. And since we already defined a virtual module with the same path, the virtual module is included while building just like I want, and of course all dependencies are resolved as well.
3) Handling the actual loading
For development, this is quite easy:
import routes from './app/routes.js';
Object.entries(routes).forEach(async ([routeId, route]) => {
if (typeof route.module === "function") {
new route.module;
} else {
const result = await import(route.module);
new result.default;
}
});
(Note that this is not the actual router code, just enough to help me with my PoC)
Well, but for the build I need something else, so I added some code specific to the build environment:
/** #remove */
const result = await import(route.module);
new result.default;
/** #endremove */
if (!isDev) {
if (typeof route.module === "string") { await __webpack_require__.e(routeId); }
const result = __webpack_require__(route.module.replace("/src", "."));
new result.default;
}
Now, the loading code for the dev environment is just stripped out, and there is another loading code that uses webpack internally. I also check if the module value is a function or string, and if it is the latter I invoke the internal require.ensure function to load the correct chunk: await __webpack_require__.e(routeId);. Remember that I named my chunks when generating the virtual module? Now thats why I still can find them now!
4) more needs to be done
Another thing I encountered is when several dynamically loaded modules have the same dependencies, webpack tries to generate more chunks with names like module1~module2.bundle.js, breaking my build. To counter this, I needed to make sure that all those shared modules go into a specific named bundle I called "shared":
optimization: {
splitChunks: {
chunks: "all",
name: "shared"
}
}
And when in production mode, I simply load this chunk manually before any dynamic modules depending on it are requested:
if (!isDev) {
await __webpack_require__.e("shared");
}
Again, this code only runs in production mode!
Finally, I have to prevent webpack renaming my modules (and chunks) to something like "1", "2" etc, but rather keep the names I just have defined:
optimization: {
namedChunks: true,
namedModules: true
}
Se yeah, there you have it! As I said this wasn't pretty but seems to work, at least with my simplified test setup. I really hope there aren't any blockers ahead of me when I do all the rest (like ESLint, SCSS etc)!

How do I update file paths in VueJs if I'm using CLI3

I'm a bit lost and need some help with VueJs. I am using Vue CLI3 and have created a new Vue project where eveything is working, no errors in the console etc. However, after running the build task, the copy in my dist folder shows as a blank page. I have learnt that this is to do with needing to update the assetsPublicPath: and remove the '/' forwards slash. To do this I have been told you have to update the config file index.js but there is no such file in my proect? I have also been told there is a config folder, but there isnt?
Therefore how do I update the following
from assetsPublicPath: '/',
to assetsPublicPath: '',
Take a look at the documentation. If you don't have the vue.config.js just create it. I would look something like this:
// vue.config.js
module.exports = {
// Any of the config options will come here. Everything you'll need is in the docs
publicPath: ''
}
Only create a vue.config.js in your project and use inside .File is automatic loaded by vue cli serve. After publish your hosting or server file will must work it.
module.exports = {
css: {
extract: true
},
publicPath: process.env.NODE_ENV === "production" ? "" : "",
outputDir: "dist"
};

Make Webpack output files relative to the input files

Let's assume I want my compiled JavaScript files to exist side-by-side with my source files, resulting in a directory structure like:
- TimeframeToolbar
- TimeframeToolbar_compiled.js
- TimeframeToolbar_src.js
- dependencies
- *contains js files that are imported by TimeframeToolbar_src.js*
Is it possible to do this with Webpack? From what I can tell, the output path only allows a single directory, meaning that the source and compiled file trees must be totally separate.
The trick is to make the keys of your entry configuration object the path you want for the output files. For example:
entry: {
'feature-1/feature-1': './dev/feature-1/feature-1.jsx',
'feature-2/feature-2': './dev/feature-2/feature-2.jsx',
},
output: {
filename: '[name].js',
path: './build',
}
This will result in the following compiled files:
./build/feature-1/feature-1.js
./build/feature-2/feature-2.js
So essentially the [name] placeholder in output.filename ends up being a directory path rather than just a file name.

Browserify adding unwanted directories

Setting up gulp for the first time. I've got it correctly compiling the files, it's just sticking them in the wrong place, and I can't quite figure out what to change to get it right.
After they compile, I have it adding the .conveyor.js suffix and then I want it to place them in the /scripts directory. But it's placing them in /scripts/src/js/ — it's adding a couple subdirectories. The raw dev files themselves are in src/js/ directories in a separate location, but I don't want that to carry over. Here's my gulp setup:
module.exports = function() {
var files = [
'./src/js/dashboard.js',
'./src/js/pages.js',
'./src/js/poll.js'
];
var tasks = files.map(function(entry) {
return browserify({
entries: [entry],
paths: ['./node_modules', './src/js/']
})
.bundle()
.pipe(source(entry))
.pipe(rename({
extname: '.conveyor.js'
}))
.pipe(gulp.dest('../scripts/'));
});
return es.merge.apply(null, tasks);
};
The way I understand it, "files" are all of the files it looks for to compile. "paths" allow you to specify directories that your require statements can be relative to so you don't have to do a bunch of period-forwardslashing. and then "dest" is where you want the files to end up. But I'm clearly misunderstanding something.
The offender is here
.pipe(source(entry))
entry is set to the exact path you are using for the files path. Hence the duplication.
source() in this isn't the source of the file, but ends up being the file that gets created.
You would want to modify the object to provide just the file name as the entry and the source path is separated. Also, you can drop the rename method, I think.

How to go back 1 folder level with __dirname?

I am using gulp-karma and facing a simple problem but cannot seems to find what i am doing wrong .
gulp.task('test', function (done) {
karma.start({
configFile: __dirname + '..\\test\\' +'\karma.conf.js',
singleRun: true
}, done);
});
Here is the code i am using and i cannot seems to go 1 level back in the folder directory . When i do the above it just append the ..\ to the folder direcotry without going 1 level back (which is the usual use of ..\). Following is the folder structure .
parent|
test|karma.conf.js
webapirole|gulpfile.js
and my folder is inside the webapirole folder . i want to go back 1 folder back and go inisde the test folder which contains the karma.conf.js file. can anyone make me understand what i am doing wrong here ?
error i am getting
[18:06:32] Starting 'tdd'...
ERROR [config]: File C:\Users\Documents\WebApiRole..\test\karma.conf.js does not exist
TL;DR
Use path.join(__dirname, '..', 'test', 'karma.conf.js'). Prevent use of slashes.
Long Answer
As a lot of answers have pointed out, using path module is probably the best way.
However, most of the solutions here have gone back to using slashes like:
path.join(__dirname+'../test/karma.conf.js')
However, by doing this, you're beating the purpose of using path. One uses path to do operations irrespective of the underlying OS (Linux, Windows etc). Just to give a bit of insight, you can do the path operations directly as string operations (like __dirname + '../test/karma.conf.js'. You do not do this because Linux uses forward slashes ( / ), Windows uses backward slashes ( \ ). This makes your application prone to errors when you port it across operating systems.
Thus, the better way would be:
path.join(__dirname, '..', 'test', 'karma.conf.js')
And of course, coming back - prevent use of slashes in your path.join, instead spread out your params.
I am using (path) NPM for the above usage......
simply require path npm in js file.Then use
let reqPath = path.join(__dirname, '../../../');//It goes three folders or directories back from given __dirname.
__dirname is just a string. you can use ../ to traverse up the folder structure and path.join to resolve the path
path = require('path')
configFile: path.join(__dirname, '../test/karma.conf.js'),
You can use Path like this
const path = require('path');
path.join(__dirname, "../");
if you are sending the path as a string,
configFile: path.join(__dirname+'../test/karma.conf.js'),
this doesn't work.
Instead you have to use a comma, (the plus sign concatenates the two strings)
configFile: path.join(__dirname, '../test/karma.conf.js'),
from Root directory
(path.join(__dirname , 'views' ,'main.html')) -> will return Root:/views/main.html
from any sub-folder of Root
(path.join(__dirname , '../views/main.html')) -> same as above
Like Pranav Totla said, hardcode the path with forward slashes ( "/" ) or backward slashes ( "\" ) makes the application prone to errors when it came across different operating systems.
Use the built in "path" module to prevent errors.
// Import "path"
const path = require('path');
// To go down on the three from index.html:
path.join(__dirname, 'css', 'style.css')
// To go up on the three from style.css:
path.join(__dirname, '..', 'img', 'cat.jpg')
// Three
root/
| |_css/
| |_img/
|
|_index.html
we can use path module to go back one level from the current directory
Example:
path.join(__dirname, '..', 'test', 'conf.js')
__dirname -- present directory
.. -- one level
test -- folder name
config.js -- file (test folder inside)
Try putting a \\ before the ..\\.
Without it, the path your generating has a folder called WebApi... as part of it. You can see this in the path being output from the error message.
Like this:
gulp.task('test', function (done) {
karma.start({ configFile: __dirname + '\\..\\test\\' +'\karma.conf.js', singleRun: true }, done);
});
You may also want to look into using the path library from npm. It makes combining paths a lot easier by handling adding and removing extra path separator characters as needed.
Here is all you need to know about relative file paths:
Starting with / returns to the root directory and starts there
Starting with ../ moves one directory backward and starts there
Starting with ../../ moves two directories backward and starts there (and so on...)
To move forward, just start with the first sub directory and keep moving forward.
this will move you 2 directory back irrespective of any operating system:
import { join, sep } from 'path';
join(__dirname, sep, "..", sep, "..");

Categories