Vue.js 3 extension breaks while using "vue-cli-service build" due to unsafe-eval - vue.js

I am developing a chrome extension using vue 3, vue-router and vuex based on Kocal's project which uses vue-cli under the hood. I used whenever possible Single File Components with extensive use of vue bindings.
Everything works perfect on development mode but I recently tried to build the application for production and I encountered this error with partial rendering:
chunk-vendors.f6de00a6.js:11 EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".
After a few days of digging, my understanding is that either webpack or vue compiling system is messing with CSP by referring/injecting code through eval scripts. As I am fairly new to this, it's hard for me to read to distinguish what I can do.
I tried different approaches:
defining $vue alias to a runtime only build in vue.config.js (supposedly removing unsafe eval by having code compiled before runtime but ending with a new error: Uncaught TypeError: Object(...) is not a function for o=Object(n["withScopeId"])("data-v-21ae70c6");)
using render() function at root
adding "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", to manifest.json
switching a component to render() to see if I have better chance with the partial rendering, but ending up with nothing being displayed although having console.log from render being executed.
Adding a config to chainWebpack splitting manifest and inline manifest on vue.config
What puzzles me is that I can't shake off the unsafe-eval, with at best a partial display, at worst a blank page. Bindings seem to be shaken off regardless and using a router-link to change page will give a blank page.
Edit: After digging through compiled code from webpack and setting minimize opt to false, it seems the error comes from a vendor: vue-i18n

The eval is likely coming from Webpack, due to an issue with global scoping.
see link for more detail https://mathiasbynens.be/notes/globalthis
Could you try adding this configuration to vue.config.js
module.exports = {
configureWebpack: {
node: {
global: false
},
plugins: [
new webpack.DefinePlugin({
global: "window"
})
]
}
};

tl;dr: Check your dependencies/packages, including those you wouldn't think they use unsafe-eval.
After digging into webpack internals and components building for vue3, here are the takeaways:
using Single File Components and default vue-cli config is ok as it will indeed just need vue runtime, so no unsolicited unsafe-eval
webpack config as below works:
module.exports = {
configureWebpack: {
plugins: [
new webpack.DefinePlugin({
global: "window" // Placeholder for global used in any node_modules
})
]
},
...
};
// Note that this plugin definition would break if you are using "unit-mocha" module for vue-cli
In the end, the issue was a dependency I was using for i18n vue-i18n#next, after removing it and switching to chrome's i18n way, it's now working.

Related

VUE: SFC - use markdown in <docs> tag - vite throwing eror

So I inherited an old (Vue 2) app that uses Styleguidist for creating style guide and documenting components...
It was running extra slow so my first task was to upgrade to using vite instead of webpack. Almost there... fixed almost all the issue, the one is outstanding though... this app uses this format of *.vue components
<template>...</template>
<script>...</script>
<style>...</style>
<docs>
Example of usage
```jsx
<MyComponent>...</MyComponent>
</docs>
where content inside is markdown, so one can write nicer documentation with code example
Now, vite is complaining that I am trying to use jsx (where I am not)...
this is the error
3:36:36 PM [vite] Internal server error: Failed to parse source for
import analysis because the content contains invalid JS syntax. If you
are using JSX, make sure to name the file with the .jsx or .tsx
extension. Plugin: vite:import-analysis
So what am I to do? How do I tell VITE to ignore that part?
The solution, as posted here, is to create a small Vite plugin that ignores the <docs> blocks.
Add this to vite.config.js:
const vueDocsPlugin = {
name: 'vue-docs',
transform(code, id) {
if (!/vue&type=docs/.test(id))
return;
return `export default ''`;
}
};
Then add the plugin to the plugins array:
export default defineConfig({
plugins: [
// vue() will be here...
vueDocsPlugin,
],
});

How to prerender a Vue3 application?

I try without success to apply a prerendering (or a SSG) to my Vue3 application to make it more SEO friendly.
I found the vue-cli-plugin-prerender-spa, and when I try it with the command line: vue add prerender-spa I have the error:
ERROR TypeError: Cannot read properties of undefined (reading 'endsWith')
After that I tried prerender-spa-plugin but I have an error when I make a npm run build:
[prerender-spa-plugin] Unable to prerender all routes!
ERROR Error: Build failed with errors.
Error: Build failed with errors.
at /Users/myusername/Workspace/myproject/node_modules/#vue/cli-service/lib/commands/build/index.js:207:23
at /Users/myusername/Workspace/myproject/node_modules/webpack/lib/webpack.js:148:8
at /Users/myusername/Workspace/myproject/node_modules/webpack/lib/HookWebpackError.js:68:3
What do you think about this? Do you have any idea?
Nuxt3 is a really powerful meta-framework with a lot of features and huge ecosystem. Meanwhile, it's in RC2 right now so not 100% stable (may still work perfectly fine).
If your project is aiming for something simpler, I'd recommend using Vitesse. It may be a bit more stable and it's probably powerful enough (check what's coming with it to help you decide).
Some solutions like Prerender also exist but it's paid and not as good as some real SSG (/SSR). Also, it's more of a freemium.
I struggled with the same error output until I found the prerender-spa-plugin-next. Then I notice the latest version of prerender-spa-plugin was published 4 years ago and prerender-spa-plugin-next is continually updating. It seems like that prerender-spa-plugin-next is a new version of prerender-spa-plugin with the same functions. So I use prerender-spa-plugin-next instead of prerender-spa-plugin then everything works fine!
Here is my step:
install the package
npm i -D prerender-spa-plugin-next
modify vue.config.js like
const plugins = [];
if (process.env.NODE_ENV === 'production') {
const { join } = require('path');
const PrerenderPlugin = require('prerender-spa-plugin-next');
plugins.unshift(
new PrerenderPlugin({
staticDir: join(__dirname, 'dist'),
routes: ['/'], //the page route you want to prerender
})
);
}
module.exports = {
transpileDependencies: true,
configureWebpack(config) {
config.plugins = [...config.plugins, ...plugins];
},
};
build
npm run build
Then check the index.html under the dist folder you can see the page is prerendered.
Further usage refers to the homepage of prerender-spa-plugin-next
Found and fix about the scss files to import.
In nuxt.config.ts use :
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: `
#import "#/assets/scss/_variables.scss";
#import "#/assets/scss/my-style.scss";
`
}
},
},
}
Now my 2 mains issue are : how to install vuetify properly, currently syles and components seems working but the JS not, for example, accordions don't expands on click.
And second topic is to have a i18n module, it seems that vue-i18N no longer works.
Thanks.

What exactly is Vue's runtime-only build and how does it differ from compiler build?

I am getting this warning:
[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
with my basic boilerplate code below. I understand it's blocking me from creating my Foo component like that but exactly does that mean and how does it different from another way of instantiating the Vue instance?
const Foo = {
template: `<div>xxx</div>`
}
const routes = [
{ path: '/foo', component: Foo },
{ path: '/', component: App}
]
const router = new VueRouter({
routes:routes
})
Vue.config.productionTip = false
new Vue({
router
}).$mount('#app')
Full build (i.e. "compiler-included")
Also known as the "full" build, "compiler-included" includes both compiler and runtime. The compiler is what allows using template strings like:
template: `<div>xxx</div>`
CDN: When using Vue through a CDN, e.g. <script src="https://unpkg.com/vue"></script>, it's typically the Full build (unless you specify otherwise).
Runtime-only
The alternative to template strings is the render function. If you used only these, you wouldn't need the compiler, and could use a runtime-only build:
render(h) {
return h('div', 'xxx')
}
Bundlers (e.g. Vue CLI): When you use a bundler like Vue CLI, it pre-builds your templates into render functions for you so that the compiler isn't needed in production. This allows for a runtime-only build.
The docs describe the runtime like this:
Runtime: code that is responsible for creating Vue instances, rendering and patching virtual DOM, etc. Basically everything minus the compiler.
So, the difference between the Full build and the Runtime-only build is the inclusion or exclusion of this template compiler.
The docs explain it this way:
If you need to compile templates on the client (e.g. passing a string to the template option, or mounting to an element using its in-DOM HTML as the template), you will need the compiler and thus the full build:
And there is this caveat to be aware of:
Since the runtime-only builds are roughly 30% lighter-weight than their full-build counterparts you should use it whenever you can
Also in the docs are configurations for using the full build with bundlers. In Webpack, for example, it's:
module.exports = {
// ...
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1
}
}
}

Nuxt ignoring babel on build process

https://nuxtjs.org/api/configuration-build#babel
I originally left the presets as default.
I then followed the suggestions on
https://github.com/nuxt/nuxt.js/issues/1776
However this dealt more with pipelines
I am just trying to get it to convert the es6 to es5 (import chief among the reasons)
I get the same result or a complete failure no matter if i add the .babelrc, adjust package.json, adjust nuxt.config.js or a combination of them.
currently i have adjusted my nuxt.config.js to:
/*
** Build configuration
*/
build: {
babel: {
presets: ['#babel/preset-env'],
configFile: false,
babelrc: false,
plugins: ['#babel/plugin-syntax-dynamic-import']
}
}
When i upload the entire .nuxt folder to my server (running plesk using phusion passenger)
I get the following error
/var/www/vhosts/website.com/app/client/server.js:1
(function (exports, require, module, __filename, __dirname) { import { stringify } from 'querystring'
My site root is
/var/www/vhosts/website.com/app/client/
The first line of server.js
import { stringify } from 'querystring
Changing this to
var stringify = require("querystring").stringify
Eliminates the error however i would need to go through page after page to remove this. My understanding is i can progamically adjust this using babel. But no matter what ive tried the file stays the same.
I did use the Nuxt CLI to automatically set up babel and webpack but using the above build config is not the default. I have attempted to play with it but i get the same result
I added babel/polyfill to try and get around the import issues without any success

Vue CLI v3 - How does it know what JS files should be chunked in a different file(s)

I am having hard times wrapping my mind around new CLI and configuration.
In the official documentation, I couldn't really find anything about CSS and how to add it as an entry point and not import it directly into an component or main.js file.
I realized that some JS files are being chunked into separate file, from main.js and the rest gets compiled where supposed to - into the app.js.
I was wondering, how does it know in the background what should be stored as "vendor" for the JS, but when I try to import some "vendor" SASS files into main.js it does not and it merges all within a single app.css file.
Can anyone tell me, how does one create/modify the vue.config.js and tell the bundler that I also want app.scss to be an entry point and vendor.scss to be another entry point.
I am unsure what are best practices for such purpose, but I always did it this way with my own webpack config...
Partial example below:
entry: {
vendor: [
'./styles/vendor.scss',
'./scripts/vendor.js'
],
app: [
'./styles/app.scss',
'./scripts/app.js'
]
}
EDIT #1
I think I got the first one...
"How does it know what should be chunked in "vendor" files?
Whatever gets imported from node_modules, it is being chunked.
What I did not figure out yet is... What if I am having my personal assets/styles/vendor directory where I #import those SASS files from NPM and do some modifications of variables or whatever.
Importing this file to main.js does not get chunked in this case... So there must be a way to tell bundler that I want everything within that directory or everything within vendor.scss file where everything is being imported, to be chunked out.
EDIT #2
I figured I can use WebPack's magical comments to import the main vendor SCSS file, such as:
import(/* webpackChunkName: "vendor" */ './assets/styles/vendor.scss')
I don't have a problem with this, but apparently the bundler does. It generates an empty vendor.[hash].js file as well.
EDIT #3
I did further research and learned that there's a command vue inspect which would output the webpack configuration.
So when making tweaks to vue.config.js, we can look a the output with this command if there's a bug or something is not working as expected.
Further more, I learned that if we specify entry directly in our vue.config.js file, that we will get an error that entry cannot be specified within our configuration file.
The following is forbidden to do so, but it's what I actually want to achieve...
// vue.config.js
module.exports = {
entry: {
app: [
'./src/main.js',
'./src/assets/styles/app.scss'
],
vendor: [
'./src/assets/styles/vendor.scss'
]
}
}
The actual proper way to do this will be an answer to my own question...
The way to achieve this is by using WebPack's Chain API.
However, if I did everything correctly, I still see a problem of generated vendor.[hash].js file with some WebPack module boilerplate. This JS file is also being injected to the index.html template.
Which leads to the same outcome as the attempt of my EDIT #2, except that we're no longer importing our Sass files within main.js
To modify entry points for my purpose of this question, we can do it the following way:
// vue.config.js
module.exports = {
chainWebpack: config => {
config
.entry('app')
.add('./src/assets/styles/app.scss')
.end()
.entry('vendor')
.add('./src/assets/styles/vendor.scss')
.end()
}
}
Note
We're not specifying the app entry JS file, which would be main.js by default, because we're not overriding the current entry point. Instead, we're extending it, so everything works as expected.
UPDATE
Until WebPack resolves this in future major releases, I found a great package - fqborges/webpack-fix-style-only-entries. It solves this issue that I was having and I'd suggest you to use it.
Final configuration would look like this:
const FixStyleOnlyEntries = require('webpack-fix-style-only-entries')
module.exports = {
chainWebpack: config => {
config
.entry('app')
.add('./src/assets/styles/app.scss')
.end()
.entry('vendor')
.add('./src/assets/styles/vendor.scss')
.end()
},
configureWebpack: {
plugins: [
new FixStyleOnlyEntries()
]
}
}
UPDATE #2
After further investigation and use of such configuration for projects, I realized that I had to use !important in styles where I had a need to override anything vendor related.
This is simply because WebPack will inject app, before vendor (both JS and CSS) and it will cause such issue.
Even if we modify the configuration from above and move app entry, below the vendor entry, it will still fail. Reason being, because we're modifying the entry point which already exists by default within vue-cli config. We're adding more entries to the app and we're adding new vendor entry.
To fix this issue of ordering, we must delete the app entirely and then create it ourselves.
const FixStyleOnlyEntries = require('webpack-fix-style-only-entries')
module.exports = {
chainWebpack: config => {
config.entryPoints.delete('app')
config
.entry('vendor')
.add('./src/assets/styles/vendor.scss')
.end()
.entry('app')
.add('./src/main.js')
.add('./src/assets/styles/app.scss')
.end()
},
configureWebpack: {
plugins: [
new FixStyleOnlyEntries()
]
}
}