publicPath does not work but __webpack_public_path__ does - javascript

I am building a Single SPA application and am facing problems toward deployment.
I am deploying each app inside a subdirectory (app-bar-mf, products-mf, and so on), and root-config (the main application) at base.
When working locally, each app is served through it's own server (localhost:9000, localhost:9001, and so on) so no subdirectories here.
When I am trying to deploy, the publicPath is not used, and assets are served through "/img/foo.png" instead of "/products-mf/img/foo.png".
If I set __webpack_public_path__ in my main.js, everything works as expected.
Any clues ?
vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === "production" ? "/products-mf/" : "/",
configureWebpack: () => {
const conf = {
externals: [
"vue",
"vuex",
"vue-router",
"vue-i18n"
]
};
if (process.env.NODE_ENV === "production") {
conf.output = { "products.js" };
}
return conf;
},
filenameHashing: false,
transpileDependencies: ["vuetify"]
};

In single-spa projects, we use systemjs-webpack-interop in order to set the __webpack_public_path__ dynamically based on the URL of where the microfrontend is hosted. This has worked well for hundreds of single-spa applications so you could consider using the same logic. This plugin is included in Vue projects generated by create-single-spa.

Related

How to bundle (minify) a Kotlin React app for deployment?

The app is created with the default template for Kotlin React apps:
uses KTS-based Gradle build scripts;
Kotlin JS plugin 1.6.10;
Kotlin wrappers for React 17.0.2.
When using ./gradlew browserProductionWebpack without any additional tweaks, it generates a build/distributions directory with:
all resources (without any modifications);
index.html (without any modifications);
Kotlin sources compiled into one minified .js file.
What I want is to:
add some hash to the generated .js file;
minify the index.html file and refer the hashed .js file in it;
minify all resources (.json localization files).
Please prompt me some possible direction to do it. Looking to webpack configuration by adding corresponding scripts into webpack.config.d, but no luck yet: tried adding required dependencies into build.gradle.kts, i.e.:
implementation(devNpm("terser-webpack-plugin", "5.3.1"))
implementation(devNpm("html-webpack-plugin", "5.5.0"))
and describing webpack scripts:
const TerserPlugin = require("terser-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin(),
new HtmlWebpackPlugin({
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true,
removeComments: true,
},
}),
],
}
}
Any hint will be appreciated.
A couple of things to put into consideration first.
If some flexible bundling configuration is needed, most likely it won't be possible to use Kotlin-wrapped (Gradle) solutions. After checking the org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig it turns out that there is only a limited set of things can be configured with it.
JS-based webpack configs require a little bit of reverse engineering to find out what is generated from the Kotlin/Gradle side and how to extend it.
For simple configurations (IMHO) there is almost no need in tweaking bundling options while using the Kotlin JS plugin.
Let's start from initial webpack configs. In my case (see the short environment description in the question above) they appear in ./build/js/packages/<project-name>/webpack.config.js. These original configs will also include all contents from JS files we create inside the ./webpack.config.d folder.
Some webpack configurations require external JS dependencies. We need to declare them in the dependencies block of build.gradle.kts. In my case they are represented with:
// Bundling.
implementation(devNpm("html-webpack-plugin", "5.5.0"))
implementation(devNpm("uglifyjs-webpack-plugin", "2.2.0"))
implementation(devNpm("terser-webpack-plugin", "5.3.1"))
implementation(devNpm("copy-webpack-plugin", "9.1.0" )) // newer versions don't work correctly with npm and Yarn
implementation(devNpm("node-json-minify", "3.0.0"))
I also dropped all commonWebpackConfigs from build.gradle.kts as they are going to be performed manually on the JS level.
All webpack JS configs (inside the ./webpack.config.d folder) are divided into 3 files:
common.js (dev server configuration for both dev and production builds):
// All route paths should fallback to the index page to make SPA's routes processed correctly.
const devServer = config.devServer = config.devServer || {};
devServer.historyApiFallback = true;
development.js:
// All configs inside of this file will be enabled only in the development mode.
// To check the outputs of this config, see ../build/processedResources/js/main
if (config.mode == "development") {
const HtmlWebpackPlugin = require("html-webpack-plugin");
// Pointing to the template to be used as a base and injecting the JS sources path into.
config.plugins.push(new HtmlWebpackPlugin({ template: "./kotlin/index.html" }));
}
and production.js:
// All configs inside of this file will be enabled only in the production mode.
// The result webpack configurations file will be generated inside ../build/js/packages/<project-name>
// To check the outputs of this config, see ../build/distributions
if (config.mode == "production") {
const HtmlWebpackPlugin = require("html-webpack-plugin"),
UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin"),
TerserWebpackPlugin = require("terser-webpack-plugin"),
CopyWebpackPlugin = require("copy-webpack-plugin"),
NodeJsonMinify = require("node-json-minify");
// Where to output and how to name JS sources.
// Using hashes for correct caching.
// The index.html will be updated correspondingly to refer the compiled JS sources.
config.output.filename = "js/[name].[contenthash].js";
// Making sure optimization and minimizer configs exist, or accessing its properties can crash otherwise.
config.optimization = config.optimization || {};
const minimizer = config.optimization.minimizer = config.optimization.minimizer || [];
// Minifying HTML.
minimizer.push(new HtmlWebpackPlugin({
template: "./kotlin/index.html",
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true,
removeComments: true,
},
}));
// Minifying and obfuscating JS.
minimizer.push(new UglifyJsWebpackPlugin({
parallel: true, // speeds up the compilation
sourceMap: false, // help to match obfuscated functions with their origins, not needed for now
uglifyOptions: {
compress: {
drop_console: true, // removing console calls
}
}
}));
// Additional JS minification.
minimizer.push(new TerserWebpackPlugin({
extractComments: true // excluding all comments (mostly licence-related ones) into a separate file
}));
// Minifying JSON locales.
config.plugins.push(new CopyWebpackPlugin({
patterns: [
{
context: "./kotlin",
from: "./locales/**/*.json",
to: "[path][name][ext]",
transform: content => NodeJsonMinify(content.toString())
}
]
}));
}
I use styled components, so no CSS configs are provided. In other things these configs do almost the same minification as being done out-of-the-box without any additional configs. The differences are:
JS sources use a hash in their name: it is referenced correctly from the index page HTML template;
HTML template is minified;
locales (just simple JSON files) are minified.
It can look like an overhead slightly because as mentioned in the beginning, it does almost the same with minimal differences from the out-of-the-box configs. But as advantage we're getting more flexible configs which can be tweaked easier further.

Sapper/Svelte.js - How to specify client-side assets location?

I have a Sapper.js application that I have successfully running on AWS Lambda. Lambda is able to deliver the server-side generated HTML created by Sapper to AWS API Gateway which then serves the app to the user. I am using S3 to host the client side assets (scripts, webpack chunks, etc). The S3 bucket is on a different domain than API Gateway.
The issue I'm having is that I need to set an asset prefix for these scripts so that Sapper can find them. Currently all of my client side scripts include relative links and look like this: <script src="/client/be33a1fe9c8bbaa6fa9d/SCRIPT_NAME.js"></script> I need to have them look like this: <script src="https://AWS_S3_BUCKET_ENDPOINT.com/client/be33a1fe9c8bbaa6fa9d/SCRIPT_NAME.js"></script>
Looking in the Sapper docs, I see that I can specify a base url for the client and server. However, changing this base url breaks my app and causes the Lambda rendering the pages to return 404 errors.
I know that when using, say, Next.js, I can accomplish this by modifying the next.config.js file to include the following:
module.exports = {
assetPrefix: "https://AWS_S3_BUCKET_ENDPOINT.com/client",
}
But I don't know how to do this in Sapper. Do I need to modify the bundler (using webpack) config? Or is there some other way?
Thank you.
I think I've figured it out.
I had to change two sapper files. First I went into sapper/dist/webpack.js and modified it like so:
'use strict';
var __chunk_3 = require('./chunk3.js');
var webpack = {
dev: __chunk_3.dev,
client: {
entry: () => {
return {
main: `${__chunk_3.src}/client`
};
},
output: () => {
return {
path: `${__chunk_3.dest}/client`,
filename: '[hash]/[name].js',
chunkFilename: '[hash]/[name].[id].js',
// change this line to point to the s3 bucket client key
publicPath: "https://AWS_S3_BUCKET_ENDPOINT.com/client"
};
}
},
server: {
entry: () => {
return {
server: `${__chunk_3.src}/server`
};
},
output: () => {
return {
path: `${__chunk_3.dest}/server`,
filename: '[name].js',
chunkFilename: '[hash]/[name].[id].js',
libraryTarget: 'commonjs2'
};
}
},
serviceworker: {
entry: () => {
return {
'service-worker': `${__chunk_3.src}/service-worker`
};
},
output: () => {
return {
path: __chunk_3.dest,
filename: '[name].js',
chunkFilename: '[name].[id].[hash].js',
// change this line to point to the s3 bucket root
publicPath: "https://AWS_S3_BUCKET_ENDPOINT.com"
}
}
}
};
module.exports = webpack;
//# sourceMappingURL=webpack.js.map
Then I had to modify sapper/runtime/server.mjs so that the main variable points to the bucket like so:
...
const main = `https://AWS_S3_BUCKET_ENDPOINT.com/client/${file}`;
...
Testing with the basic sapper webpack template, I can confirm that the scripts are loading from the s3 bucket successully. So far this all looks good. I will mess around with the sapper build command next to make it so I can pass these hacks in as command line arguments so I don't have to hardcode them every time.
Now, I'm not sure if this will hold up as the app becomes more complicated. Looking into the sapper/runtime/server.mjs file, I see that the req.baseUrl property is referenced in several different locations and I don't know if my hacks will cause any issues with this. Or anywhere else in sapper for that matter.
If anyone with more experience with the Sapper internals is reading, let me know in the comments if I screwed something up 👍

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"
};

Vue js babel build error after installing prerender-spa-plugin

So I have a vue-js application and today I started using prerender-spa-plugin to generate some static pages for better SEO. When I run npm run build, everything works perfect, no errors. Now when I want to run the development server with npm run serve, I get the following error (only a part of it)
error in ./src/main.js
Module build failed (from ./node_modules/babel-
loader/lib/index.js):
Error: .plugins[0] must include an object
at assertPluginItem (/Users/user/Desktop/app/node_modules/#babel/core/lib/config/validation/option-assertions.js:231:13)
So I guess the problem has to do with the babel plugin loader. So I commended every part of my code using prerender-spa-plugin, but I still get the same error. I hope someone can point me to the right direction.
My babel.config.js
const removeConsolePlugin = []
if(process.env.NODE_ENV === 'production') {
removeConsolePlugin.push("transform-remove-console")
}
module.exports = {
presets: [
'#vue/app'
],
plugins: [removeConsolePlugin]
}
My vue.config.js
const path = require('path');
const PrerenderSpaPlugin = require('prerender-spa-plugin');
const productionPlugins = [
new PrerenderSpaPlugin({
staticDir: path.join(__dirname, 'dist'),
routes: ['/', '/documentation'],
renderer: new PrerenderSpaPlugin.PuppeteerRenderer({
// We need to inject a value so we're able to
// detect if the page is currently pre-rendered.
inject: {},
// Our view component is rendered after the API
// request has fetched all the necessary data,
// so we create a snapshot of the page after the
// `data-view` attribute exists in the DOM.
//renderAfterElementExists: '[data-view]',
}),
}),
];
module.exports = {
configureWebpack: (config) => {
if (process.env.NODE_ENV === 'production') {
config.plugins.push(...productionPlugins);
}
}
}

Cleanest way to declare env vars with Express

I'm trying to find out there the best solution about how to store environment vars for Node using Express. Many tutorials suggest to move the data into a .json file, here is an example but the don't config environment vars, they define new vars such as:
{
"test": {
"facebook_app_id": "facebook_dummy_dev_app_id",
"facebook_app_secret": "facebook_dummy_dev_app_secret",
},
"development": {
"facebook_app_id": "facebook_dummy_dev_app_id",
"facebook_app_secret": "facebook_dummy_dev_app_secret",
},
"production": {
"facebook_app_id": "facebook_dummy_prod_app_id",
"facebook_app_secret": "facebook_dummy_prod_app_secret",
}
}
The problem I've find to this approach is that I can't define vars in it, because all elements must be inside double quotes ( " ) so I can't do:
{
process.env.PORT = 3000,
process.env.NODE_APP = XXX
....
}
I've tried to do some tricky things like defining a .js file and store there the vars, something like this:
module.exports = {
env: {
USER:"www-data",
NODE_ENV:"development",
PORT:"3002",
....
LOG_FILE:"error.log"
}
}
In this case, I have not found the way to use those vars inside env, something like:
module.exports = {
env: {
...
process.env.APP_NAME:"application.name",
process.env.NODEJS_DIR:"/var/nodejs",
process.env.APP_DIR: `${this.NODEJS_DIR}/${this.APP_NAME}/current`, //Not working
process.env.APP_DIR:process.env.NODEJS_DIR}/${this.APP_NAME}/current`, //Not working, and verbose, ugly code
....
}
}
Is there a better way to solve this?
Thanks
You can use dotenv module to achieve this.
So in your root directory, create 2 files (depending on how many different environments you might have) named development.env and production.env
In your app, do this:
var env = process.env.NODE_ENV || 'development';
require('dotenv').config({
path: './' + env + '.env'
});
Then you could access all the environment variables you have defined in the respective env file.
Something like this you are looking for.
app.configure('production', function(){
// production env. specific settings
});
As another example of forming the config file
// config file in /config/general.js
var path = require('path'),
rootPath = path.normalize(__dirname + '/..'),
env = process.env.NODE_ENV || 'development';
if (env == 'development') {
process.env.DEBUG = 'http';
}
var config = {
development: {
name : 'development',
root: rootPath,
port: 3000
},
production: {
name : 'prod',
root: rootPath,
port: 3000
}
};
module.exports = config[env];
Think about the dev-ops team, how they will deploy your software? They won't touch the code, and the configuration file should not contain any logic. So the .js file containing configurations is wrong. You have to provide them an interface to configure the execution. That may be a parameter, or if the configuration is complex, then it should be extracted to a configuration file.
My approach is to load app.json by default. If the dev-ops engeneer provides NODE_ENV=customenv, then load app.customenv.json configuration file. This way the .json file won't expose non-relevant development configurations to the users.
APP_NAME is not part of the environment, but part of your software configuration. This parameter should be configured in the .json configuration file.
Directories can be relevant to the software directory, it defined in the __dirname variable.
The app.json file should be loaded by a configuration module. You can use one of the open-source projects available, for example nconf.

Categories