How to build individual component chunks with Vite to load with defineAsyncComponent - vue.js

I'm not sure this is even possible, but it looks like some of the moving parts are there.
GOAL:
Create a library of single file Vue 3 components that will compile into separate chunks using Vite, and be dynamically/async loaded at runtime. The app itself will load, then load up a directory of individually chunk'd elements to put in a toolbox, so afterward each element could be updated, and new ones could be added by putting new chunks in the same path.
So far, I can create the separate chunks within the vite.config as follows:
...
build: {
rollupOptions: {
output: {
...buildChunks()
}
}
}
...
The buildChunks function iterates over SFC files in the ./src/toolbox path and returns an object like...
{
'toolbox/comp1':['./src/toolbox/comp1.vue'],
'toolbox/comp2':['./src/toolbox/comp2.vue'],
'toolbox/comp3':['./src/toolbox/comp3.vue'],
...
}
This all works, but I'm not sure how to make that next leap where the server code dynamically loads all of those generated chunk files without explicitly listing them in code. Also, since the Vite build adds an ID in the file name (e.g. comp.59677d29.js) on each build, referencing the actual file name in the import can't be done explicitly.
So far what I've considered is using defineAsyncComponent(()=>import(url)) to each of the files, but I'd need to generate a list of those files to import...which could be done by building a manifest file at build time, I guess.
Any suggestions? Is there a better approach?

Related

webpack creating 100's of js chunks crippling performance for a single page view with vue cli

We have a vuejs 2 project.
We needed some function of dayjs.
We abstracted the dayjs into a single utility file and following the guide ended up with this:
import { i18n } from '#/plugins/i18n';
import dayjs from 'dayjs';
import calendar from 'dayjs/plugin/calendar';
dayjs.extend(calendar);
export default async (date: Date, locale?: string): Promise<string> => {
import(`dayjs\locales\${locale}`)
const time:any = i18n.t('datetime.dict');
return dayjs(date).locale(userLocale).calendar(null, {
lastDay: `[${time.yesterday} ${time.at}] LT`,
sameDay: `[${time.today} ${time.at}] LT`,
nextDay: `[${time.tomorrow} ${time.at}] LT`,
lastWeek: `[${time.last}] dddd [${time.at}] LT`,
nextWeek: `dddd [${time.at}] LT`,
sameElse: 'LLLL'
});
};
The app works perfectly fine, but vuejs is outputting every single langauge file from dayjs as a chunk and also adding the prefetch script tag to the index.html.
Has anyone else hit this issue and is the above code snippet wrong or should there be a more precise import?
Here is a screenshot of only some of the dayjs chunks.. each is completely the same with different langauge strings in... but we didn't even import the locale setting options yet.
#MichalLevĂ˝ gave the direction to the answer to this issue.
Essentially, webpack cannot know which single file to import when you say:
import(`dayjs\locales\${locale}`)
So.. it just creates a js chunk for every single file it finds. Which is not overly bad on its own.
When you combine this behaviour with vue-cli's default prefetch behaviour, this is when things get bad... the result is that every single chunk that webpack creates ends up being a "pre-fetched" line in the HTML head in the outputted index HTML file.
So when there is 400 chunks from a dynamic import (in this case locales from dayjs) then there is a prefetch for all of them.
It depends on your setup but.. for us, the app is also a PWA so there is effectively no requirement for prefetching as the PWA will also download... in fact it doubles the downloads.
The answer for us was to be more specific on the prefetch:
https://cli.vuejs.org/guide/html-and-static-assets.html#prefetch
And also, as we only needed a few languages, import only what was needed but being specific and not passing a variable to map to a path

Ways to import a JSON file in public folder in Vue-CLI

I want to import a JSON file to use it, I need it to modify it in the future so I put it in public folder not assets, When I refer to it like this import JSON from ../../public/Data.json it works but I don't think so after building project can be resolved because after building there is no public folder. So I tried this :
let addr = process.env.BASE_URL;
import JSON from `${addr}Data.json`;
But It throws an error : SyntaxError
I'm confused now which way is the best and is there another way ?
The assets in the public folder are copied as is to the root of the dist folder. In your code, you can reference it just as /Data.json (if your app is deployed at the root of the domain).
E.g
async someMethod() {
const baseUrl = process.env.BASE_URL;
const data = await this.someHttpClient.get(`${ baseUrl }/Data.json`);
}
If you want to import the JSON as you have tried, I suggest to put it somewhere in the src folder and import from there
E.g.
import data from '#/data/someData.json'
console.log(data);
I came across this because I was doing a stand alone SPA that I wanted to run with no DB and keep the config in a JSON file. The import statement above works great for a static conf file, but anything imported like that gets compiled with the build, so even though your someData.json will exist in the public folder you won't see any changes in your dist because it's actually reading a JS compiled file.
To get around this I:
Convert the JSON file into a simple JS variable in a conf.js file:
e.g.
var srcConf={'bGreatJSON':true};
In index.html, did
<script src='./conf.js'>
Now that the JS variable has been declared in my Vue component I can just look for the window.srcConf and assign it if it exists in mounted or created:
if(typeof window.srcConf!='undefined')
this.sConf=window.srcConf;
This also avoids the GET CORS issue that others posts I've seen runs into, even in the same directory I kept getting CORS violations trying to do an axios call to read the file.

How do I create a list of data based on actual twig files within project

I am creating an app via NPM using https://www.npmjs.com/package/grunt-twig-render. It essentially is twig.js. I'm keeping it slim, so right now there isn't any other php or anything like that. Just npm / twig.js and other npm packages, including Grunt.
Here's what I'm trying to do. Right now, I have a bunch of twig files within subfolders of a directory.
What I'd like to do is generate a list of data of the .twig files in that subdirectory. Something like this may work well
files: [
{
"name": "file1.twig"
"path": "/folder1"
},
{
"name": "file2.twig"
"path": "/folder1"
},
{
"name": "file3.twig"
"path": "/folder1"
}
]
But I'm not super picky. Just seeing if anyone has found a way to create a list of files within a folder via npm, or twigjs, or something similar.
If it generates a .json file, that would be ideal. This would be part of a build process, so doing that via Grunt would work well too.
Thank you in advance
You can register a custom grunt task in your Gruntfile.js which utilizes the shelljs find method to retrieve the path of each .twig file.
shelljs is a package which provides portable Unix shell commands for Node.js. It's find method is analogous to the Bash find command.
The following steps describe how to achieve your requriement:
cd to your project directory and install shelljs by running:
npm i -D shelljs
Configure your Gruntfile.js as follows:
Gruntfile.js
module.exports = function(grunt) {
// requirements
var path = require('path'),
find = require('shelljs').find;
grunt.initConfig({
// other tasks ...
});
/**
* Custom grunt task generates a list of .twig files in a directory
* and saves the results as .json file.
*/
grunt.registerTask('twigList', 'Creates list of twig files', function() {
var obj = {};
obj.files = find('path/to/directory')
.filter(function(filePath) {
return filePath.match(/\.twig$/);
})
.map(function(filePath) {
return {
name: path.basename(filePath),
path: path.dirname(filePath)
}
});
grunt.file.write('twig-list.json', JSON.stringify(obj, null, 2));
});
grunt.registerTask('default', ['twigList']);
};
Explanation
Both shelljs and nodes built-in path module are required into Gruntfile.js.
Next a custom task named twigList is registered.
In the body of the function we initialize an empty object and assign it to a variable named obj.
Next, the shelljs find method is invoked passing in a path to the subdirectory containing the .twig files. Currently the path is set to path/to/directory so you'll need to redefine this path as necessary.
Note: The find method can also accept an array of multiple directory paths as an argument. For example:
find(['path/to/directory', 'path/to/another/directory'])
The find method returns an Array of all paths found inside the given directory, (many levels deep). We utilize the Array's filter() method to return only filepaths with a .twig file extension by providing a regex (\.twig$) to the Strings match method.
Each item of the resultant Array of .twig filepaths is passed to the Array's map() method. It returns an Object with two properties (name and path). The value for the name property is obtained from the full filepath using nodes path.basename() method. Similarly, the value for the path property is obtained from the full filepath using nodes path.dirname() method.
Finally the grunt file.write() method is utilized to write a .json file to disk. Currently the first argument is set to twig-list.json. This will result in a file named twig-list.json being saved to the top-level of your project directory where Gruntfile.js resides. You'll need to redefine this output path as necessary. The second argument is provided by utilizing JSON.stringify() to convert obj to JSON. The resultant JSON is indented by two spaces.
Additional info
As previously mentioned the shelljs find method lists all files (many levels deeep) in the given directory path. If the directory path provided includes a subfolder(s) containing .twig files that you want to exclude you can do something like the following in the filter() method:
.filter(function(file) {
return filePath.match(/\.twig$/) && !filePath.match(/^foo\/bar/);
})
This will match all .twig files, and ignore those in the sub directory foo/bar of the given directory, e.g. path/to/directory.

How to add to Aurelia CLI's list of watched directories

All of my program code is in the src/ directory of my project as per usual, but I have a number of json data files in a sibling folder to src/ called data/. I'd like it if BrowserSync would automatically refresh the page after detecting changes to my json files in data/ as it does to html/css/ts in src/, but I'm not sure where (or how) in aurelia.json I'd add that directory in to be watched (even though nothing in there needs to be processed or transpiled).
Just figured it out, was looking in the wrong place. I appended a line to the watch function in aurelia_project/tasks/run.ts to look like:
let watch = function() {
gulp.watch(project.transpiler.source, refresh).on('change', onChange);
gulp.watch(project.markupProcessor.source, refresh).on('change', onChange);
gulp.watch(project.cssProcessor.source, refresh).on('change', onChange);
gulp.watch("data/**/*.json", refresh).on('change', onChange); <=====
};
and that did the trick.

npm - managing vendors for distribution

I am playing with Gulp.js & npm recently, it's great. However, I do not really get the idea of npm as a package manager for packages which will get pushed for dist.
Let's go with an example.
I want to download the latest jquery, bootstrap and font-awesome so I can include them into my project. I can simply download them from their websites and get the files to include. Another option seems to be a packet manager, i.e. NPM.
However, my node_modules directory is huge due to other packages such as gulp, and it's not nested at all. What would be the easiest way to move selected packages to another dir - for example src/vendors/
I was trying to achieve that by gulp task simply copying specified files from node_modules and moving them to a specified dir, nonetheless in the long run it's almost the same as manually copying files since I have to specify not only the input directory, but also the output directory for each single package.
My current solution:
gulp.task('vendors', function() {
var jquery = gulp.src(vendors.src.jquery)
.pipe(gulp.dest(vendors.dist.jquery));
var bootstrap = gulp.src(vendors.src.bootstrap)
.pipe(gulp.dest(vendors.dist.bootstrap));
return merge(jquery, bootstrap);
});
vendors = {
src: {
jquery: 'node_modules/jquery/dist/**/*',
bootstrap: 'node_modules/bootstrap/dist/**/*'
},
dist: {
jquery: 'src/resources/vendors/jquery',
bootstrap: 'src/resources/vendors/bootstrap'
}
}
Is there an option to do it faster and/or better?
There's no need to explicitly specify the source and destination directory for each vendor library.
Remember, gulp is just JavaScript. That means you can use loops, arrays and whatever else JavaScript has to offer.
In your case you can simply maintain a list of vendor folder names, iterate over that list and construct a stream for each folder. Then use merge-stream to merge the streams:
var gulp = require('gulp');
var merge = require('merge-stream');
var vendors = ['jquery/dist', 'bootstrap/dist'];
gulp.task('vendors', function() {
return merge(vendors.map(function(vendor) {
return gulp.src('node_modules/' + vendor + '/**/*')
.pipe(gulp.dest('src/resources/vendors/' + vendor.replace(/\/.*/, '')));
}));
});
The only tricky part in the above is correctly figuring out the destination directory. We want everything in node_modules/jquery/dist to end up in src/resources/vendors/jquery and not in src/resources/vendors/jquery/dist, so we have to strip away everything after the first / using a regex.
Now when you install a new library, you can just add it to the vendors array and run the task again.