I build a little front thanks to VueCLI (+ Vuetify).
I wanted to render a button where onClick open a new tab with a pdf located in the folder's tree of the project.
I have an error and after hours looking why, it's seems to be a webpack conf to modify.
I finally read this answer on S/O ; Serving static pdf with react webpack file loader
But I got an error saying include: paths -> paths is not defined
I have to admit that I have no clue how webpack works behind the scene so any help would be find.
You probably don't need Webpack for this: you can just put it in your static folder and link to it pdf. If you want to go the Webpack route, put it in your src/assets directory and import it and add it to data object:
import pdf from '../assets/mypdf.pdf'
//...
data: () => ({ pdf })
//...
Then use the link in your component:
<a :href="pdf">pdf</a>
You probably added include: paths to the loader config but you didn't define it, instead of modifying your image loader, add a new one for PDFs:
{
test: /\.(pdf)(\?.*)?$/,
loader: 'url-loader',
options: {
name: utils.assetsPath('[name].[hash:7].[ext]')
}
}
You can change [name].[hash:7].[ext] if you want, say you want to add a pdfs directory in assets instead of using the base assets directory you would use: pdfs/[name].[hash:7].[ext]
Related
I have an application that uses UIkit, Less for local styling, and Vite for frontend tooling (bundling and whatnot). I'm not sure that this is relevant, but this is a Vue 2/Webpack application that I'm upgrading to Vue 3/Vite.
Per UIkit's Less documentation, we import UIkit's uikit.theme.less file in our project's base less file. UIkit's stylesheets have some relative paths to SVG files that get run through less's data-uri function (examples below). That worked fine with Webpack, but with Vite it's not quite working. As I understand it, for small files, data-uri will UTF-8 encode the asset and essentially inline it--at least that's what we got in our Webpack bundles. But with our Vite build, it seems these relative image paths aren't being resolved, data-uri hence falls back to a url(), and we get 404s for the images.
For example, in UIkit's code the relative path to a spinner image is defined here; it's used in a select.uk-select. And here that path is passed to a .svg-fill mixin (here). When we bundle the code using Vite, or run a local dev server, the result is:
background-image: url(../../images/backgrounds/form-select.svg);
That, of course, doesn't load because the "images" directory is relative to the form.less file. In Webpack, the output was as expected:
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23001c30' d='M12 1 9 6h6zM12 13 9 8h6z'/%3E%3C/svg%3E");
For this type of question, I would normally include an HTML/CSS/JS snippet; however, I don't think SO supports Vite. As such, I'm including a small Stackblitz that minimally demonstrates the issue: https://stackblitz.com/edit/vite-esqmqc?file=main.js Please see main.js, style.less, and note that there's a 404 error in the console complaining about the aforementioned form-select.svg file.
In case the question isn't clear, how can I get Vite to resolve images which are relative to a dependency in node_modules?
Thanks in advance.
A workaround is to configure resolve.alias to point ../../images to uikit/src/images (which resolves to the uikit package under node_modules/). This tells Vite how to resolve the problematic relative image paths.
The resolve.alias config is passed to #rollup/plugin-alias as entries. Each entry can have a custom resolver that can be used to only replace imports from UIKit. However, it requires that the import of uikit.theme.less be in its own file so that the custom resolver can correctly identify the importer in order to determine when to replace the import.
Put the import of uikit.theme.less in its own file, and import that from main.js (not from style.less):
// style.less
// #import './node_modules/uikit/src/less/uikit.theme.less'; ❌ move to own file
// my-uikit.less
#import './node_modules/uikit/src/less/uikit.theme.less';
// main.js
import './style.less';
import './my-uikit.less';
Create vite.config.js with the following configuration:
// vite.config.js
import { defineConfig } from 'vite';
import { fileURLToPath } from 'url';
import { basename } from 'path';
export default defineConfig({
resolve: {
alias: [
{
find: '../../images',
replacement: '',
customResolver(updatedId, importer, resolveOptions) {
// don't replace if importer is not our my-uikit.less
if (basename(importer) !== 'my-uikit.less') {
return '../../images';
}
return fileURLToPath(
new URL(
'./node_modules/uikit/src/images' + updatedId,
import.meta.url
)
);
},
},
],
},
})
demo
Using Vue CLI, and Vue 2.
Anyone knows how to build the project using relative paths, so I can place it in any subfolder in my server and it will work? (for example www.mysite.com/subfolder/)
I've tried with
// vue.config.js
module.exports = {
publicPath: "",
};
And it builds the paths relative (ie, js/app.js instead of /js/app.js), but the app wont' load. Nothing shows on the page.
Strangest thing is that all files are loaded correctly (I can check on network tab in Chrome devtools), no JS errors, etc. So the page is loading all the files but it seems like it's refusing to mount the app when using relative paths.
I know that I can add the absolute path to the build process but that's not what I need. My client needs to be able to move the files freely from one subfolder to another and the app should work without the need to recompile
PS: Also tried building the project with Vite and Vue 3, same problem.
Thanks!
Alright, looks like all that's needed is:
build with a relative publicPath (empty string)
// vue.config.js
module.exports = {
publicPath: "",
};
add a <base> tag to the final HTML...
<base href="/subfolder/" />
For Vite, the export should be
// vite.config.js
export default {
...
base: "",
};
I have the following code
<a href="../../assets/all-work/proto/pdf-test.pdf" download>Download</a>
It's inside a vue component which is located in src/components/Home/Proto.vue and the file is located in src/assets/all-work/proto/pdf-test.pdf, when I click download it downloads something completely illegible, why's that and how could I fix it?
I'm going to assume you're using Vue CLI.
The default configuration generated by the Vue CLI has special handling for images. To get the same thing working for PDFs you need to configure it yourself.
Firstly, you'll need to explicitly use require on your href:
<a :href="require('../../assets/all-work/proto/pdf-test.pdf')" download>Download</a>
In some scenarios, such as the src attribute of an <img>, the wrapping with require is performed automatically. For an <a href> it isn't.
You'll then run into a Webpack loader error because Webpack has no idea what to do with a .pdf file.
To fix that you'll need to configure a file-loader in vue.config.js:
module.exports = {
chainWebpack (config) {
config.module.rule('pdf')
.test(/\.pdf$/)
.use('file-loader')
.loader('file-loader')
}
}
If you don't already have a vue.config.js file then you'll need to create one. It goes at the top level, next to your package.json.
If you do already have a vue.config.js then you'll need to merge the chainWebpack property in, alongside your existing configuration.
By default the file will be renamed to a hash based on its content. It you want to retain the original file name you can configure that instead:
chainWebpack (config) {
config.module.rule('pdf')
.test(/\.pdf$/)
.use('file-loader')
.loader('file-loader')
.options({
name: '[name].[ext]'
})
}
More details on the name option can be found at:
https://webpack.js.org/loaders/file-loader/#name
I'm trying to integrate a vue project that I built with the vue cli into an existing .net app. I'm very new to vue, so I'm trying to follow guides and such, but am left with lots of questions.
While trying to compile this, I found that the vue cli-service node module has the following for setting the main.js file located in it's base.js file.
webpackConfig
.mode('development')
.context(api.service.context)
.entry('app')
.add('./src/main.js')
.end()
.output
.path(api.resolve(options.outputDir))
.filename(isLegacyBundle ? '[name]-legacy.js' : '[name].js')
.publicPath(options.publicPath)
I need to override this since my .net app doesn't have a src directory and the usage of this vue app won't follow that path structure. I'm not seeing a way to do it in my vue.config.js file. I would expect that if I can override it, that would be the spot.
I could overwrite the base.js file where this exists, but when a co-worker runs npm install, they would get the default value rather than what I have. The only option I see there is checking in all the node modules to git which we really don't want to do.
For anyone in a similar situation, I found what worked for me. It's not the ideal solution due to the fact that it forces you to build into a js folder. That resulted in the file being put in Scripts\build\vue\js. Would be nice to be able to just dump it in the vue folder, but at least this works. Code below.
vue.config.js
module.exports = {
publicPath : "/",
outputDir: "Scripts/build/vue", //where to put the files
// Modify Webpack config
// https://cli.vuejs.org/config/#chainwebpack
chainWebpack: config => {
// Not naming bundle 'app'
config.entryPoints.delete('app'); //removes what base.js added
},
// Overriding webpack config
configureWebpack: {
// Naming bundle 'bundleName'
entry: {
quote: './Scripts/Quote/index.js' //where to get the main vue app js file
},
optimization: {
splitChunks: false
}
},
filenameHashing: false,
pages: {
quoteApp: { //by using pages, it allowed me to name the output file quoteApp.js
entry: './Scripts/Quote/index.js',
filename: 'index.html'
}
}
}
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()
]
}
}