Vue + Webpack: exclude config.js from being packed works, but it does not load - vue.js

What I want:
In my Vue project, when doing a vue run build, everything is packed (webpack) into the dist directory.
I could deploy this entire package to the test server, but since the test server needs another set of credentials (for database etc.), one file, namely config.js, must be different on each environment.
My strategy:
exclude config.js from being packed into the app.12314...js bundle
define config.js as being emitted unaltered as a file (via webpack's file-loader)
What I did:
In the Vue component files which need config data, config is included via:
<script>
import config from '#/config/config.js';
</script>
I created a vue.config.js file for modifying the default webpack settings like this:
const path = require("path");
// vue.config.js
module.exports = {
publicPath: '/',
configureWebpack: {
module: {
rules: [
{
test: /config.*config\.js$/,
use: [
{
// exclude config file from being packed. The config file should simply reside as a plain file on the
// file system, since it must be replaced with the target environoment specific config file on the server,
// e.g. for setting the correct database connection
loader: 'file-loader',
options: {
name: 'config/config.js'
},
}
]
}
]
}
}
}
What I expected:
config.js will be emitted as a plain file, not bundled => dist folder will contain a bundled app....js file and a separate config.js file
I can deploy the content of the dist folder to the test server and the generated code will take care of loading config.js adequately, so that the components can use the config data
Problem:
Granted, config.js is now emitted as a file, so my dist folder looks like this:
.
├── config
│   └── config.js
├── css
│   ├── app.eb377513.css
│   └── chunk-vendors.2da46af1.css
├── favicon.png
├── index.html
└── js
├── app.724607ed.js
├── app.724607ed.js.map
├── chunk-vendors.426dad42.js
└── chunk-vendors.426dad42.js.map
As you can see, a separate file config.js! All other JS code has been bundled into app.724607ed.js. So far, so good.
However, the application on the test server would not load config.js, so every component which tries to use the variables in it, fails.
I noticed that in Chrome's developer tools, there is no network traffic for config.js. Apparently, the webpack code does not try to actually load the file from the file system. I expected this to be the case automatically.
what am I doing wrong, what am I missing?
is this the best practice to exclude config file from being packed?

Apparently, Webpack's file-loader does emit the file separately (good), but does not add code for loading it in the webapp (bad). I assumed that file-loader would take care of inserting the correct <script> tag in the index.html file, so that:
the separately emitted config.js file will be loaded
on the dev side, the syntax for using the config.js is the same as for the other (packed) artifacts, like this:
(in the Vue component file:)
<script>
import backend from '#/utils/backend.js'; // <-- normal way of requiring modules in Vue component
import config from '#/config/config.js'; // <-- would work the same, but actually will load emitted file
</script>
Unfortunately, seems I was wrong. Since Webpack does not care about loading the file in the webapp, I switch to a manual way, like this:
Step 1: Emit as a separate file
This one is important, since I want the config.js file to be separate (not packed and minified along with the rest of the Vue app). I already described how to do this in the question (define a separate rule for config.js and use file-loader for it).
Step 2: Take care yourself of loading the config file in the webapp
Since the file-loader does not add loading code, make it manually. In index.html, insert the following line:
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.png">
<script type="text/javascript" src="config/config.js" ></script> <-------- insert this line!
<title>replayer-gui4</title>
</head>
Step 3: change config.js to be compatible with legacy importing method (without ES2015 module)
Now, the commonJS syntax is not viable anymore because config.js should be loaded by the browser. And I am not sure how to mix ES2015 type module loading (with native import syntax) with Webpack code (which would replace all import's with Webpack loading code). So I decided to switch to a legacy method: let config.js simply register a global (yuck!) variable like in old times:
var config = (() => {
return {
databaseCredentials: ...
};
})();
Consequently, in each Vue module consuming config settings, adapt the "import" code - simply remove the imports because the variable config will be avalailable globally anyway (after our changes in config.js in this step):
<script>
// import config from '#/config/config.js'; // <-- commented out - or just REMOVE this line
</script>
Step 4: Trigger Webpack to consider config.js at all
The only caveat now is that since config.js is never referenced in a Javascript file in an import statement, Webpack would not "find" the file and would never take it into consideration. Which means that config.js also never would get emitted.
So, force Webpack to handle it, in App.vue:
<script>
import configDummy from '#/config/config.js';
</script>
Note that we never use the configDummy reference (variable), this is just that Webpack finds it - and applies its rules to the file. The rule in this case would be: emit it as a separate file in the dist folder (i.e. produce a dist/config/config.js file)
That's it! We used Webpack's file-loader so that config.js is not minified and bundled into the application bundle, but kept as a separate file. Further we took care of loading it via index.html which means the config settings are globally available.
What I do not like about this solution is that the nice methodology with import methods is corrupted for this special file. But I found no simple solution which just would make Webpack take care of these things.
If somebody has a suggestion, I would be glad to hear!
Update 09.04.2019: No, this is NOT an ideal solution. This works for the packaging/deploying (the npm run build part), but not for the development server (the npm run serve part). In order for the dev server to work, I had to copy config.js into the path public/config/config.js, so that the dev server can find it at the place which index.html is telling it, i.e. config/config.js (this is resolved relative to the index.html file).
So, again: it works, but sort of clumsy. And I do not like this solution.

Related

Change default directory in Snowpack Vue app

I want to use Snowpack for my Vue 3 app. Currently, I've initialized the Vue 3 app using the following command:
npx create-snowpack-app my-app --template #snowpack/app-template-vue
By default, Snowpack creates a directory structure like this:
public/
src/
App.vue
index.js
package.json
README.md
snowpack.config.js
I need to restructure things slightly. The reason why is I'm moving an existing app to Snowpack. My challenge is I need to move to a structure like this:
public/
pages/
App.vue
index.js
package.json
README.md
snowpack.config.js
In short: 1) rename src to pages and 2) move index.js up to the same level as package.json. When I make this change, Snowpack throws a warning that says "mounted directory does not exist". It prints out the files it's looking for, which, Snowpack is clearly still looking in the src directory. My thinking was to review the snowpack.config.js file.
I looked at the mount property in the snowpack.config.js file. I changed mount.src.url to /pages. However, that didn't work. I also tried changing the property to just /, which also didn't work.
What do I need to change to tell Snowpack to look in the current directory instead of the src directory for the index.js file?
That directory layout is possible with a mount config that specifies . as the mount point:
// snowpack.config.js
module.exports = {
mount: {
'.': {url: '/dist'}
},
}
Then the root index could import the SFC from <projectRoot>/pages/:
// <projectRoot>/index.js
import App from './pages/App.vue'
...

How do you add css to a Kotlin JS project?

I created a new Kotlin/JS Gradle project using the wizard in IntelliJ.
I'm unclear how I'm supposed to add css to the project. The documentation explains how to enable css webpack support, but it doesn't actually say how to add the css file into your project (i.e., how to use the file).
For example, in a normal project, you would just import it in a javascript file. Since I am writing in Kotlin, how do I do it now?
The current documentation is not very precise about this. There are actually two cases:
Importing CSS from existing packages
You can pretty easily import CSS files from other Node-modules using the require() function:
import kotlinext.js.require
import kotlinx.browser.document
import react.dom.h1
import react.dom.render
fun main() {
require("bootstrap/dist/css/bootstrap.css")
render(document.getElementById("root")) {
h1 { +"Hello"}
}
}
For this to work, you need to specify cssSupport.enabled = true in your Gradle build, just like described in the documentation. CSS imported this way will be processed by Webpack.
Incorporating your own CSS into the Webpack build
This seems to be a bit tricky right now. The KotlinJS plugin doesn't copy any resources to the Webpack's build directory (build/js/packages/<project_name>) by default and I didn't find any obvious configuration option for this. To solve it, you have to tell Webpack where it can find your styles:
Create webpack.conf.d directory in project's root and put inside some JS file containing:
config.resolve.modules.push("<your_stylesheet_dir>");
This config will be picked up by the KotlinJS plugin and merged into the generated build/js/packages/<project_name>/webpack.config.js. With this configuration you can just require() project's styles like in the example above. It is kind of mentioned in the documentation.
Alternatively you can tweak the Gradle build, so it copies the stylesheets into the Webpack's build dir:
task("copyStylesheets", Copy::class) {
from(kotlin.sourceSets["main"].resources) {
include("styles/**")
}
into("${rootProject.buildDir}/js/packages/${kotlin.js().moduleName}")
// kotlin { js { moduleName = "xyz" }} has to be set for this to work
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinJsDce::class) {
dependsOn("copyStylesheets")
}
Simply sticking a CSS file into main/resources and referencing it in index.html worked for both browserDevelopmentRun and serving the production build, statically. The CSS file appears in build/distributions.
My build:
kotlin("js") version "1.7.20"
index.html
<link rel="stylesheet" href="index.css">
index.css is in the same resource folder as index.html.
This also works for images anything else, apparently.

Provide enviroment variables for production vue build

I would like to achieve following:
a Vue application is build with npm build,
then the /dist result is copied to some environment
in this enviroment I have some static setting file with name=value settings
the Vue application should read this setting from local folder where it is running or default to some setting
What is the best way to do this.
If you want "to inject" some settings to the bundled app so I think it can be possible only with another js file (globalConfig.js) with global object like:
window.myAppSettings = {
MY_VARIABLE: 'some_value'
}
Which will be copied somehow to your dist folder on a particular environment.
You should also prepare your app to reference that file:
Firstly, add this settings object as external lib in vue.config.js
module.exports = {
chainWebpack: config => {
config.externals({
'my-app-settings': 'myAppSettings'
})
}
}
So you can get your settings in code:
import mySettingsObject from 'my-app-settings'
//...
let myValue = mySettingsObject.MY_VARIABLE
Add reference to globalConfig.js in index.html file in the head section:
<script src="<%= BASE_URL %>globalConfig.js"></script>
Local Development
Probably you will need some default settings to be able to debug your app locally. In this case you can create localConfig.js in your public folder with some default values.
Then change your script in index.html to this:
<script src="<%= BASE_URL %><%= VUE_APP_GLOBAL_SETTINGS_VERSION %>Settings.js"></script>
Then create two files in the project root .env.local and .env.production:
// .env.local
VUE_APP_GLOBAL_SETTINGS_VERSION=local
and
// .env.production
VUE_APP_GLOBAL_SETTINGS_VERSION=global
So when you run npm run serve it will load your local config and your app will load localSettings.js.
And when it builds with npm run build it will load globalSettings.js because building uses a production mode by default.
If you created your project using Vue CLI 3 you can do like this.
Make sure your settings file is named .env and place it in your project root.
In the .env file, your variables should be prefixed with "VUE_APP_"
VUE_APP_SOMEKEY=SOME_KEY_VALUE.
Access them with process.env.*.
console.log(process.env.VUE_APP_SOMEKEY) // SOME_KEY_VALUE
Here's some more info on evironment variables in vue: Vue CLI 3 - Environment Variables and Modes
EDIT:
Sorry. I probably misunderstood your question. This solution will not work if your settings file is in the dist folder.

Webpack 'publicPath' with express static

I'm trying to understand webpack output.publicPath now. I'm reading webpack official docs now, but it dosen't help me.
So here is webpack.config.js
import webpack from 'webpack';
export default {
output: {
filename: 'bundle.js',
path: '/dist',
publicPath: '/assets' // what's this for?
},
plugins: [
// ...
]
};
So i guess, this makes all files reference /assets which is set to publicPath. like prefix.
When if i want to server static file in /assets with express server, i should makes /assets as static like app.use('/assets', express.static(__dirname + '/assets')) .
Then what is the purpose of publicPath ? is it just prefix for path?
I'm a bit late, but in case anyone else looked at this question like me and wished it had an answer -
This publicPath is used by webpack as an alias by which you can access your built files. When you also attach it to express static file serving as you've done above, you allow any files placed there by webpack to be requested via that path.
For example, if you have /assets as your publicPath and a file foo.js that is built during your webpack build, you can then access it by hitting localhost:[port]/assets/foo.js
I've spent so much time trying to wrap my head around path vs publicPath that my head nearly explodes. Here is what I understand (correct me if I'm wrong):
publicPath specifies the URL that Webpack needs to reference from the perspective of the index.html file.
For example:
module.exports = {
output: {
path: path.resolve(__dirname, 'dist/assets')
publicPath: '/assets/
}
}
This means the bundle is located at the dist/assets directory in your file system, but a request to it will look like localhost:3000/assets/bundle.js.
Inside the index.html, the script tag will look like this:
<script type="text/javascript" src="assets/bundle.js"></script>
It is even more important when you serve assets from external resources like a CDN.
What you do with Express static middleware is irrelevant AFAIK. But I still set the assets folder as the root directory from which to serve static files.

Run Aurelia CLI app from a directory

I would like to deploy my app to a virtual directory. I have been unable to figure out the correct configuration needed to run the app locally with a similar structure. For example, I'd like to run it from:
http://localhost:8080/demos
I have tried every combination of adding "demos" to publicPath and contentBase in my webpack config. The errors just between 404's on static assets and router errors from Aurelia.
It is documented by Aurelia router, you can add base tag to index.html header, <base href="/demos">, and set router root config.options.root = "/demos"; in configureRouter().
In addition, if your bundled js files are indeed served from directory, you need to modified baseDir in 2 places of aurelia.json: platform.baseDir and build.targets[0].baseDir.