Webpack2 Lazy Loading multiple modules - lazy-loading

I need to lazy load multiple files at the same time.
Would be nice to lazy load a modul + its language files
Is there something possible like this?
import(/* webpackChunkName: "chart.js" */ ['chart.js','chart.de.js']).then((Chart, Chart2) => { }

Found the solution here https://survivejs.com/webpack/building/code-splitting/#dynamic-import
Promise.all([
import("lunr"),
import("../search_index.json"),
]).then(([lunr, search]) => {
return {
index: lunr.Index.load(search.index),
lines: search.lines,
};
});

Related

Can rollup-plugins access the AST created by previous plugins in the plugin chain?

We use multiple rollup-plugins that parse their input to an AST. As they run on the same files, each file is parsed multiple times. Can this be optimized, so that each file is parsed only once? Minimal example:
// rollup.config.js
import {createFilter} from '#rollup/pluginutils';
import {simple} from 'acorn-walk';
import {attachComments} from 'astravel';
import {generate} from 'astring';
export default {
input: 'src/main.js',
output: {file: 'bundle.js', format: 'cjs'},
plugins: [{
name: 'plugin1',
transform(code, id) {
const comments = [];
const ast = this.parse(code, {onComment: comments});
attachComments(ast, comments);
simple(ast, {
Identifier(n) {
// rewrite wrong to right
if (n.name === 'wrong') n.name = 'right';
}
});
return {
code: generate(ast, {comments: true}),
ast,
map: null /* minimal example, won't create a source map here */
};
}
}, {
name: 'plugin2',
transform(code, id) {
const comments = [];
const ast = this.parse(code, {onComment: comments});
attachComments(ast, comments);
simple(ast, {
CallExpression(n) {
// rewrite mylog(...) to console.log(...)
if (n.callee.type === 'Identifier' && n.callee.name === 'mylog') {
n.callee = {
type: 'MemberExpression',
object: {type: 'Identifier', name: 'console', start: n.start, end: n.end},
property: {type: 'Identifier', name: 'log', start: n.start, end: n.end},
computed: false,
start: n.start,
end: n.end
}
}
}
});
return {
code: generate(ast, {comments: true}),
ast,
map: null /* minimal example, won't create a source map here */
};
}
}]
};
Now I understand that transform() can return an AST, so that parsing doesn't have to happen twice. And I understand that this.parse() uses the rollup-internal acorn instance. My simple mind thought that this.parse() could return the AST created by previous transform() calls, if available. But I assume that all sorts of demons await on that road, e.g. when this.parse() was called with different options.
Is there a different way achieve what I described? A different hook maybe?
I would love to not have all plugins in one and switching them on and off via options (I see that this would be a solution, but a really cumbersome one).

How do you dispose a reaction defined in a class constructor?

In some of my models I need to define a reaction in the constructor like this:
constructor() {
//...some code
const dispose = reaction(
() => this.items.length,
count => {
this.setItemCount(count);
}
);
}
I am using a reaction rather than a computed (#computed get itemCount()) because loading items into state is an expensive (lots of data over network) operation and so I need to persist the most recent value so that it can be used throughout the app. The reaction is to update the value if the count changes when the items are loaded into state.
So, with the above in mind, I'm wondering when/how I would dispose of the reaction? I want to avoid memory leaks. I'm open to alternative ways of accomplishing what I need although I would prefer a reactive vs imperative approach.
Three ways to go about this.
Dispose of reaction after it is done its job.
constructor() {
const dispose = reaction(
() => this.items.length,
(count, reaction) => {
this.setItemCount(count);
if(count === 100) reaction.dispose()
}
);
}
Some other action disposes it for you. Like a button click. Or another reaction. Whatever you need it to be, actually.
class myStore {
disposer
constructor() {
this.disposer = reaction(
() => this.items.length,
(count) => this.setItemCount(count)
);
}
myButtonClick = () => {
this.disposer()
}
}
Create a "deconstructor" method in your class that is meant to be called when you don't "need" this class/store anymore. You can use this method for dumping in anything that needs a cleanup before safely passing things to garbage collector.
class myStore {
disposers = []
constructor () {
this.disposers.push(reaction(
() => this.items.length,
(count, reaction) => {
this.setItemCount(count);
if(count === 100) reaction.dispose()
}
))
}
deconstructor() {
this.disposers.forEach((disposer) => disposer())
}
}
You are responsible for calling this deconstructor too. Typically you will be calling it on component unmount. Hook example below:
function Example() {
const [store] = useState(() => new myStore())
useEffect(() => {
return () => store.deconstructor()
}, [])
return <App/>
}
If store is global/context, you can call destructor in a frame component (a component that is always mounted in app's lifecycle), so it is run when user exits the app. I am not so sure, however, how needed is this step specifically for Mobx disposables, maybe someone can comment on this. Does not hurt to do it, though.
NB. Actually you should be doing number 3 at all times anyway, because it could be that due to some reasons, condition at 1. (or 2.) might not manage to trigger and you are left with unneeded reaction ticking in the background.
I'm using an array of disposables + specific method to dispose them.
It's looking like:
class MyClass {
...
disposables = [];
...
constructor () {
// constructor stuff
this.disposables.push(reaction(
() => this.items.length,
count => {
this.setItemCount(count);
}
))
}
...
disposeAll = () => {
this.disposables.forEach(dispose => dispose());
}
}
This method is not useful if you want to dispose specific reaction. But in this case you can you map instead of an array.

How to add custom blocks / containers in Vuepress?

I've set up a website in VuePress and I found that it supports markdown-it's :::danger, :::tip, :::info etc to generate custom containers.
I was wondering if this could be extended in a way, to use for example :::card or :::example or whatever you want.
I found https://github.com/posva/markdown-it-custom-block, but can't find out how to implement it.
This is what've got in my config.js
markdown: {
// options for markdown-it-anchor
anchor: { permalink: false },
// options for markdown-it-toc
toc: { includeLevel: [1, 2] },
extendMarkdown: md => {
md.use(require("markdown-it-container"), "card", {
validate: function(params) {
return params.trim().match(/^card\s+(.*)$/);
},
render: function(tokens, idx) {
var m = tokens[idx].info.trim().match(/^card\s+(.*)$/);
if (tokens[idx].nesting === 1) {
// opening tag
return (
"<card><summary>" + md.utils.escapeHtml(m[1]) + "</summary>\n"
);
} else {
// closing tag
return "</card>\n";
}
}
});
}
}
Any advice is much appreciated!
The script you have will work with ::: card, in order to get it to work change
extendMarkdown: md => {...
to
config: md => {...
This took me a while to figure out. It's a version conflict - that's why it's currently not working.

How to remove attributes from tags inside Vue components?

I want to use data-test attributes (as suggested here), so when running tests I can reference tags using these attributes.
<template>
<div class="card-deck" data-test="container">content</div>
</template>
The element will be found using:
document.querySelector('[data-test="container"]')
// and not using: document.querySelector('.card-deck')
But I don't want these data-test attributes to get to production, I need to remove them. How can I do that? (There are babel plugins that do that for react.)
Thanks!
The solution (for a Nuxt.js project), provided by Linus Borg in this thread, is:
// nuxt.config.js
module.exports = {
// ...
build: {
// ...
extend(config, ctx) {
// ...
const vueLoader = config.module.rules.find(rule => rule.loader === 'vue-loader')
vueLoader.options.compilerModules = [{
preTransformNode(astEl) {
if (!ctx.dev) {
const {attrsMap, attrsList} = astEl
tagAttributesForTesting.forEach((attribute) => {
if (attrsMap[attribute]) {
delete attrsMap[attribute]
const index = attrsList.findIndex(x => x.name === attribute)
attrsList.splice(index, 1)
}
})
}
return astEl
},
}]
}
}
}
where tagAttributesForTesting is an array with all attributes to be removed, like: ["data-test", ":data-test", "v-bind:data-test"].
For those of you who want to know how to do this is vanilla Vue 3, read on.
According to the Vue CLI documentation, the correct way to override a loader's options is to use the chainWebpack method within your Vue configuration (within your vue.config.js file):
module.exports = {
chainWebpack(config) {
config.module
.rule('vue')
.use('vue-loader')
.tap((options) => {
options.compilerOptions.modules = [{
preTransformNode(element) {
if (process.env.NODE_ENV !== 'production') return
const { attrsMap, attrsList } = element;
if ('data-test' in attrsMap) {
delete attrsMap[attribute];
const index = attrsList.findIndex(x => x.name === attribute);
attrsList.splice(index, 1)
}
return element;
}
}];
return options;
});
}
};
For your particular use case, I think the most maintenance free option would be to use a pattern matching strategy to remove test attributes. This would keep you from having to add every new test attribute to the list of blacklisted attributes:
{
preTransformNode(element) {
if (process.env.NODE_ENV !== 'production') return
const { attrsMap, attrsList } = element;
for (const attribute in attrsMap) {
// For example, you could add a unique prefix to all of your test
// attributes (e.g. "data-test-***") and then check for that prefix
// using a Regular Expression
if (/^data-test/.test(attribute)) {
delete attrsMap[attribute];
const index = attrsList.findIndex(x => x.name === attribute);
attrsList.splice(index, 1)
}
}
return element;
}
}
Note that the attributes will include any Vue directives (e.g. "v-bind:") that you attach to them, so be sure to compensate for that if you decide to identify your test attributes using unique prefixes.
I think it would be best to mention that, just like #ahwo before me, I drew my inspiration from Linus Borg's suggestion on the Vue forums.
P.s. With Vue, it's possible to create attributes that have dynamic names. I think this would be useful to know for anyone who is adding attributes for testing

How to make observable from multiple event in Rxjs?

How are you. I am newbie of Rxjs. I am not sure how to merge observable from different event. I integrated Rxjs with Vue.js
export default {
name: 'useraside',
data: function () {
return {
searchKey: '',
isPublic: true
}
},
components: {
User
},
subscriptions () {
return {
// this is the example in RxJS's readme.
raps: this.$watchAsObservable('searchKey')
.pluck('newValue')
// .filter(text => text.length > 1)
.debounceTime(500)
.distinctUntilChanged()
.switchMap(terms => fetchRaps(terms, this.userdata._id, this.isPublic))
.map(formatResult)
}
}
}
Now event comes from searchKey changes, now I would like to subscribe same observable when isPublic value change.
So I would like to get raps whenever searchKey changes or isPublic changes.
Thanks.
You could use the merge operator and keep using the this.isPublic in your switchMap, as Maxime suggested in the comment.
But I'd rather go with a nice a pure dataflow where you listen for the two values and consume them in your handlers. Something like
Rx.Observable.combineLatest(
this.$watchAsObservable('searchKey').pluck('newValue'),
this.$watchAsObservable('isPublic').pluch('newValue'),
([searchKey, isPublic]) => ({ searchKey, isPublic })
)
.dedounceTime(500)
.distinctUntilChanged()
.switchMap(({ searchTerm, isPublic }) => fetchRaps(searchTerm, this.userdata._id, isPublic))
Or event better is you can change the initial data structure to something like :
data: function () {
return {
searchConfig: {
searchKey: '',
isPublic: true
}
}
},
you can then remove the combineLatest and only watch the searchConfig property.
The benefit of this implementation is that your dataflow is pure and doesn't depend on any external context (no need for the this.isPublic). Every dependency is explicitly declared at the beginning of the dataflow.
If you want to go even further, you can also watch the userdata and explicitly pass it down the dataflow :)