Vue used with Webpack removes app root from DOM - javascript

This question is related to
run vue with webpack
I would be grateful for answer why I can't simply initialize Vue & Webpack app by creating:
index.js
import Vue from 'vue';
let app = new Vue({
el: '#app',
data: {
message: 'message'
}
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue Playground</title>
</head>
<body>
<div id="app">
{{ message }}
</div>
</body>
</html>
Here's my webpack config file:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.join(__dirname, 'dist')
},
plugins: [new HtmlWebpackPlugin({
template: './src/index.html'
})]
};
In this way, root (#app) is simply removed from HTML file by Vue.
I'm new to Vue, and will be glad for explanation. I have a learning manner, that before learning complex solutions like #vue/cli I wish to analyze dependencies of it.
Should I work only with vue-loader? As far as I know, it's used to parse .vue files, but I don't need one here.
Thank You in advance.

By default, when used with Webpack, the runtime only build of Vue is used. If you want to have in-DOM template (your case where Vue template is included directly inside HTML file) or string templates (template component option), those are compiled at runtime (when the page is loaded into browser) and that requires Vue build which includes compiler.
Take a look to the docs on how to configure Webpack to use compiler build of Vue - Runtime + Compiler vs. Runtime-only

Related

javascript file is not working in vue cli html template

I'm using vue cli to create a multipage web app.
The file structure is like below:
- public
- index.html
- tables.html
- us.html
- guide.html
- src
- main.js
and the main.js is:
import Vue from 'vue'
import router from './router'
Vue.config.productionTip = false
console.log('hello world!');
let appExists = document.querySelector('#app') != undefined;
if(appExists){
new Vue({
router,
render: h => h(App),
mounted(){
console.log('hi!');
}
}).$mount('#app')
}
What I'm looking for:
I want main.js file to be loaded into tables.html file.
What is happening:
main.js is not working in any file except index.html.
What I have done:
As I searched, I realized that vue cli loads the main.js only in index.html and not in any other file. when I try to load the main.js using <script src="/src/main.js"></script> in tables.html, I always get the error Uncaught SyntaxError: expected expression, got '<'. I tried some solutions like https://cli.vuejs.org/config/#pages but it didn't change anything at all.
And the question is:
What should I do to use main.js in other files?
I figured it out. Everything in vue cli works with the combination of webpack plugins.
The plugin which connects the files together is called "HtmlWebpackPlugin".
First, install the plugin using:
npm i html-webpack-plugin
Then you can tell this plugin: "Hey I have another page in my project! Please connect main.js to it" by writing the code below in vue.config.js:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
configureWebpack: {
plugins: [
new HtmlWebpackPlugin({
filename: 'tables.html',
template: './public/tables.html',
}),
new HtmlWebpackPlugin({
filename:'index.html',
template: './public/index.html',
}),
]
}
}
Hope it will help other people who are facing the same issue!

Why doesn't importing scripts from <script> tags work under html-loader?

I'm developing a website with Webpack 5, html-webpack-plugin, and html-loader.
Here's my index.html:
<!DOCTYPE html>
<html>
<head>
<title>test page</title>
</head>
<body>
<p>hello there</p>
<script type="application/javascript" src="../script/index.ts">
</script>
</body>
</html>
Here's my index.ts:
console.log('hi');
document.body.append('this isnt hmr but its not bad for a static site generator');
document.body.append('ree');
Here's the relevant section of my webpack.config.js:
module.exports = async (env) => {
return {
// ...
module: {
rules: [
// ...
{
test: /\.html$/i,
use: ['html-loader'],
},
],
},
plugins: [
// ...
new HtmlPlugin({
template: '../path/to/index.html',
filename: 'index.html',
})
],
// ...
};
};
When I try to compile this, I get the following error:
ERROR in Error: webpack-internal:///608:2
document.body.append('this isnt hmr but its not bad for a static site generator');
^
ReferenceError: document is not defined
- 608:2 eval
webpack-internal:///608:2:1
- index.html:21 Object.608
/home/laptou/website/client/source/page/index.html:21:1
- index.html:71 __webpack_require__
/home/laptou/website/client/source/page/index.html:71:41
- 673:3 eval
webpack-internal:///673:3:34
- index.html:48 Object.673
/home/laptou/website/client/source/page/index.html:48:1
- index.html:71 __webpack_require__
/home/laptou/website/client/source/page/index.html:71:41
- index.html:81
/home/laptou/website/client/source/page/index.html:81:18
- index.html:82
/home/laptou/website/client/source/page/index.html:82:12
- index.js:320 HtmlWebpackPlugin.evaluateCompilationResult
[client]/[html-webpack-plugin]/index.js:320:28
Why is the code in my index.ts being evaluated, instead of just being bundled, and how do I make this stop happening?
Edits:
I have looked at this question, but I feel my question is different because I am getting a different error. I already have a Babel loader in my chain, so all of the code should be perfectly digestible, plain JS.
I have looked at this question and its answer also, but I do not want to use Parcel because I could not get its glob imports to work correctly and consistently, and it does not have all of the features and community support that Webpack has.
I see a couple of issues in your webpack.config.js, and your index.html template.
Your template directly tries to include the index.ts, which is typescript.
But the plugin will inject the bundled script, so you shouldn't specify the script tag
yourself.
You have typescript in your project, but no rule specified with a loader for typescript.
You mention you are using 'html-webpack-plugin',
but the plugin you specify is spelled differently.
A final hint, on the error:
Whenever I've encountered 'docucument is not defined',
it has been because the webpack loader rules were configured inconsistently,
which causes webpack to execute the loaders on the wrong kinds of input,
which leads to weird 'the world is not defined!' kinds of errors.
Otherwise 'document is not defined' happens when clientside typescript/javascript
is erroneously included in a SSR(server-side-rendering) and/or nodeJS/server-side context.

where to find or how to set htmlWebpackPlugin.options.title in project created with vue cli 3?

I wanted to set title to my webpage created with vue cli 3 and thus looked into public/index.html. There, I found <title><%= htmlWebpackPlugin.options.title %></title>.
How do I set and modify htmlWebpackPlugin.options.title in vue cli 3 project?
Looking at the popularity of the question, I decided to add an elaborate answer with references to make it more authentic and complete. I have also created an article on this topic and covered this topic in this and this courses.
Though the question is looking for setting htmlWebpackPlugin.options.title, the ultimate effect is changing the title of the web-page.
1. Most convenient and trivial solution
The simplest way to do this is to modify the public/index.html and hard-code the title.
<!DOCTYPE html>
<html lang="">
<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.ico">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
This is the default public/index.html that is generated by vue cli. And in this, you just need to change
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
to
<title>Title of your choice</title>
2. Change the name field in package.json
Another simple solution is to change the "name": "your-project-name". However, there are many restrictions on the name you can use in package.json. You can read more about this here. Basically, package.json must contain a name and that must be lowercase and one word, and may contain hyphens and underscores.
3. Using pages field in vue.config.js
vue.config.js is an optional file that you can add to provide additional configurations to Vue CLI and this file, if present, will be automatically loaded by Vue CLI. You need to create vue.config.js in the root folder - the folder containing you package.json file.
According to Vue documentation, you can use pages field to define entrypoint for multi-page app. However, you can also use this to define title for single page app as well. Create vue.config.js in the root directory and add pages field to your exports as follows:
module.exports = {
pages: {
index: {
// entry for the page
entry: 'src/main.js',
title: 'My Title',
},
}
}
Note that if you are already running development server, this change will be reflected only when you stop and restart the development server. In other words, these changes will not be hot reloaded.
4. Chaining Webpack
You can chain Webpack in vue.config.js as shown below
module.exports = {
chainWebpack: config => {
config
.plugin('html')
.tap(args => {
args[0].title = "My Vue App";
return args;
})
}
}
Note that similar to solution 3, this change will be reflected only when you stop and restart the development server, in case you are already running development server. In other words, these changes will not be hot reloaded.
5. Modify title in lifecycle hooks using JavaScript
The next solution in the list is to use JavaScript to modify the title. You can do this either in mounted lifecycle hook of your root component or if you want different title for different routes, you can do the same for components loaded by each route.
<script>
export default {
data() {
return {
//
};
},
mounted() {
document.title = 'new title'
}
}
</script>
6. Use Vue Meta
Finally you can use Vue Meta to manage all metadata for your Vue app including title. First you need to add Vue Meta to your project and then use metaInfo field as shown below to configure metadata for your page or route.
{
metaInfo: {
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ title: 'My title'}
]
}
}
Conclusion
The first 4 solutions are static ways of changing your title or in other words you can't change your title at runtime using these ways. Also all of these are not hot reloaded. The last 2 options use JavaScript and can manipulate the title at runtime.
create a file vue.config.js at the root
//vue.config.js
module.exports = {
chainWebpack: config => {
config
.plugin('html')
.tap(args => {
args[0].title = "My Vue App";
return args;
})
}
}
see https://cli.vuejs.org/guide/webpack.html#modifying-options-of-a-plugin
Update the name property in your package.json file
{
"name": "URL-friendly_app-name",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
...
},
"devDependencies": {
...
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Update:
The above mentioned method will only work if you use a URL friendly title.
There are a couple of other ways to do it
From the Vuejs official documentation Pages Configuration, you can use the html plugin configuration to specify the title for different pages
Use the environement variables Modes and Environment Variables to hold your app/page title. I personally prefer and use this method.
.env (or any .env.[mode])
VUE_APP_NAME=Application flixible name
And this is how you call it in different places in your app
AnyComponent.vue (as a data property)
TypeScript
appName: string = process.env.VUE_APP_NAME
Javascript
appName: process.env.VUE_APP_NAME
anyHTML.html
<%= process.env.VUE_APP_NAME %>
Just correcting one of the scripts with the green check
<script>
export default {
data() {
return {
//
};
}
mounted() {
document.title = 'new title'
}
}
</script>
If you are using the vue-pwa plugin, the name is set within the site.webmanifest file.
I have simply assigned value fo htmlWebpackPlugin.options.title something like this.
<!DOCTYPE html>
<html lang="">
<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.ico">
<title><%= htmlWebpackPlugin.options.title="WebStore" %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script type="module" src="https://unpkg.com/ionicons#5.5.2/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons#5.5.2/dist/ionicons/ionicons.js"></script>
</body>
</html>
Simple and I think the best way is to update the name inside the packer.json and you need to restart your app
package.json file:
{
"name": "my title app",
}
If you're using vue router you can do it through the router's index file. In the router.beforeEach method you just have to change the document.title to your routes title.
const DefaultPageTitle = "Default title"
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "home",
meta: {
title: "Your Title"
},
},
}
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
setPageTitle(to);
next();
});
function setPageTitle(to: RouteLocationNormalized) {
const hasMetaTitleString = typeof to.meta.title == 'string';
if (!hasMetaTitleString) {
return document.title = DefaultPageTitle;
}
return document.title = to.meta.title as string;
}

import Stencil in Svelte

I have a Monorepo with a svelte project and a Stencil component library. On the Stencil website they very clearly describe how to integrate the library with, for example, Angular
import { defineCustomElements } from 'test-components/loader';
defineCustomElements(window);
Super easy. But now I would like to use it too in a Svelte project ..... not so super easy anymore :(
When I try to do something similar as described above I get serious errors
fbp/dist is where the Stencil files are.
When I build my Stencil project first and copy my dist into the public folder and load ./dist/fbp.js in the head of index.html it all works. But it would be a lot easier if I could include it similar as it does with Angular. Any suggestions?
Update: Added emitCss which gives
Somewhere at the end it stats: Error: Unexpected token (Note that you need plugins to import files that are not JavaScript)
UPDATE: With the fixes of #Sambor, Svelte is now able to download the web component, which unfortunately fails
I have created a new project and I manage to reproduce the same problem.
At first, I was thinking is related to typescript and I've tried bunch of plugins in rollup like : #tscc/rollup-plugin-tscc, rollup-plugin-typescript but it didn't work.
I also tried rollup-plugin-amd with same results...
Then I've tried to change the main output format and use es instead of iife.
This way it also required to change the output to a directory instead of file (because of multiple file generation).
And surprisingly this way it seems to work.
here is my code:
/// index.html
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Test</title>
<link rel='stylesheet' href='build/bundle.css'>
<script type="module" defer src='build/main.js'></script>
</head>
<body>
</body>
</html>
Note: main.js is imported as module.
/// main.js
import App from './App.svelte';
import { applyPolyfills, defineCustomElements } from '../my-comp/loader';
applyPolyfills().then(() => {
defineCustomElements(window);
});
const app = new App({ target: document.body });
export default app;
/// rollup.config
import svelte from 'rollup-plugin-svelte';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import postcss from 'rollup-plugin-postcss';
import autoPreprocess from 'svelte-preprocess';
import json from '#rollup/plugin-json';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'es',
name: 'app',
dir: 'public/build'
},
plugins: [
json(),
svelte({
// Enables run-time checks when not in production.
dev: !production,
// Extracts any component CSS out into a separate file — better for performance.
css: css => css.write('public/build/bundle.css'),
// Emit CSS as "files" for other plugins to process
emitCss: true,
preprocess: autoPreprocess()
}),
resolve({
browser: true,
dedupe: importee => importee === 'svelte' || importee.startsWith('svelte/')
}),
commonjs(),
postcss({
extract: true,
minimize: true,
use: [
['sass', {
includePaths: ['./node_modules']
}]
]
}),
// In dev mode, call `npm run start` once the bundle has been generated
!production && serve(),
// Watches the `public` directory and refresh the browser on changes when not in production.
!production && livereload('public'),
// Minify for production.
production && terser()
],
watch: {
clearScreen: false
}
};
function serve() {
let started = false;
return {
writeBundle() {
if (!started) {
started = true;
require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
}
}
};
}
Note: I took my config from another svelte project (you can ignore uninteresting plugins)
Now it seems to be working fine, but I think is just the starting point :) because there are some known issues with stencil itself which I come across;
core-3d1820a5.js:97 TypeError: Failed to fetch dynamically imported module: http://localhost:57231/build/my-component.entry.js
core-3d1820a5.js:863 Uncaught (in promise) TypeError: Cannot read property 'isProxied' of undefined
https://github.com/sveltejs/sapper/issues/464
https://github.com/ionic-team/stencil/issues/1981
same with react: Unable to integrate stenciljs component in React application
This is not the completely working solution, but I thought it may help you for the next steps...
I’m still having the same issue in 2020. Surprisingly, the webpack template is working fine. Switching to that for now, until this is resolved.
https://github.com/sveltejs/template-webpack

How to use relative urls in angular 1.5 across all modules

I'm using angular 1.5 and webpack to generate a dist folder with my angular aplication.
This is my folder structure:
--app
--dist
--loginComponent
--login.html
--homeComponent
--home.html
...
--app.css
--app.js
--index.html
And this is my index.html:
<!doctype html>
<html lang="es" ng-app="firmaDigitalApp" ng-strict-di>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="dist/app.css">
<script src="dist/app.js"></script>
</head>
<body>
<app></app>
</body>
</html>
Everything good so far, the problem is that app.js generated is not accessing the html by its path in a relative manner so it can't find the resource (instead of looking for home.html in context/dist/homeComponent.html, it's looking for it in context/homeComponent.html).
Just in case, this is homeModule.js:
require('./administradorComponent/administradorModule.js');
require('./definicionComponent/definicionModule.js');
var controller = require('./homeController');
var homeModule = angular.module('homeModule', [ 'administradorModule', 'definicionModule' ]);
homeModule.component('homeComponent',
{
templateUrl : 'homeComponent/home.html',
$routeConfig : [
{
path : '/administrador/...',
name : 'Administrador',
component : 'administradorComponent',
useAsDefault : true
}]
});
homeModule.controller('homeController', [ '$rootScope', '$location', '$log', controller ]);
What can I do so my angular application resources use relative paths to access each other so I don't have to type dist/ over and over again?
Let me know if you need more information.
Rather than using templateUrl, I'd recommend using html-loader to make your templates be built into your output file (i.e. app.js). If you set that up, you can just do:
template: require("homeComponent/home.html") // Resolves to a string
This will cause your bundle to take a little longer to download up front, as all the templates will get loaded too, but you won't have to wait for the templates to load asynchronously when the component is initialized, so it evens out in my opinion.

Categories