Is there any way of building my svelte or react application in a way, that the three.js module (which I usually import using npm) will be declared as a script tag which will call the module from a CDN? I would like to keep the advantages of a framework but also be able to reduce my final bundle size, since most of my bundle contains three code.
Thank you for your wisdom
There are two ways to go about your goal of reducing bundle size:
Importing from a CDN (your suggestion)
Code-splitting
Importing from a CDN
To keep semantics of ESModules, you may simply replace your current three.js imports with a URL from an npm CDN, like unpkg:
Pros
Cons
No extra configuration needed
Slower to load, as browser needs to spin up new connections to access third-party CDN
Asynchronously
<script>
// App.svelte
import('https://unpkg.com/three#0.133.1/build/three.min.js').then(({ default: THREE }) => {
// your code here
});
</script>
Synchronously
Note: Importing like this blocks the rest of your script from loading while three.js is downloading, which defeats the purpose of the whole shebang. It's just here for completeness
<script>
// App.svelte
import { default as THREE } from 'https://unpkg.com/three#0.133.1/build/three.min.js';
// your code here
</script>
Code-splitting
This method takes advantage of the fact that you're already using a bundler (probably rollup, vite, or webpack). This answer will focus on rollup as it's the default used in svelte's examples.
Pros
Cons
Faster to load, as browser can use existing connections to access first-party resources
More complicated to get set up
Asynchronously
In your rollup.config.js file, ensure output.format is set to 'esm' & output.dir is set instead of output.file
// rollup.config.js
import svelte from 'rollup-plugin-svelte';
import resolve from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import postcss from 'rollup-plugin-postcss';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/index.js',
output: {
sourcemap: !production,
format: 'esm',
name: 'app',
dir: 'public',
},
plugins: {
// your plugins
svelte({
compilerOptions: {
dev: !production,
},
}),
postcss({
extract: 'bundle.css',
}),
resolve({
browser: true,
dedupe: ['svelte'],
}),
commonjs(),
}
}
<script>
// App.svelte
import('three').then(({ default: THREE }) => {
// your code here
});
</script>
Note: There is no synchronous way due to how code-splitting is evaluated at compile time. Plus it doesn't make much sense to do it like that anyways.
Yes, you can do the following:
In your "index.html" file, you can import the js file from a CDN as follow:
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
Then, in the file where you want to use it, which could be a React component for instance, you can do the following:
const THREE = window.THREE;
Which would replace your import statement, which would have been import * as THREE from "three"; or import THREE from "three";
Related
How can I programmatically render a react app in gulp and node 12?
I taking over and upgrading an old react (0.12.0) app to latest. This also involved upgrading to ES6. The react code itself is done, but we also need to prerender the application (The app is an interactive documentation and must be crawled by search engines).
Previously, the gulp build process ran browserify on the code and then ran it with vm.runInContext:
// source code for the bundle
const component = path.resolve(SRC_DIR + subDir, relComponent);
vm.runInNewContext(
fs.readFileSync(BUILD_DIR + 'bundle.js') + // ugly
'\nrequire("react").renderToString(' +
'require("react").createElement(require(component)))',
{
global: {
React: React,
Immutable: Immutable,
},
window: {},
component: component,
console: console,
}
);
I am suprised it worked before, but it really did. But now it fails, because the source uses ES6.
I looked for pre-made solutions, but they seem all targeting old react versions, where react-tools was still around.
I packaged the special server-side script below with browserify & babel and then ran it using runInNewContext. It does not fail but also not output any code, it just logs an empty object
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './index';
const content = renderToString(<App />);
I found tons of articles about "server-side rendering", but they all seem to be about rendering with express and use the same lines as the script above. I can't run that code directly in gulp, as it does not play well with ES6 imports, which are only available after node 14 (and are experimental).
I failed to show the gulp-browserify task, which was rendering the app component directly, instead of the server-side entrypoint script above. In case anyone ever needs to do this, here is a working solution.
Using vm.runInNewContext allows us to define a synthetic browser context, which require does not. This is important if you access window anywhere in the app.
src/server.js:
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './index';
const content = renderToString(<App />);
global.output = content;
above script serves as entry point to browserify. Gulp task to compile:
function gulpJS() {
const sourcePath = path.join(SRC_DIR, 'src/server.js');
return browserify(sourcePath, { debug:true })
.transform('babelify', {
presets: [
["#babel/preset-env", { targets: "> 0.25%, not dead" }],
"#babel/preset-react",
],
})
.bundle()
.pipe(source('server_output.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(sourcemaps.write('.'))
.pipe(dest(BUILD_DIR));
}
The generated file can now be used by later tasks, e.g. to insert the rendered content into a HTML file.
const componentContent = fs.readFileSync(path.join(BUILD_DIR, 'server.js'));
const context = {
global: {
React: React,
Immutable: Immutable,
data: {
Immutable
},
},
window: {
addEventListener() { /* fake */ },
removeEventListener() { /* fake */ },
},
console,
};
vm.runInNewContext(componentContent, context);
const result = context.global.output;
I have a bundle building script using Rollup but inside my code I have a simple JSON "require" import that "rollup" is not resolving for some reason, and there are no errors, anyone is aware of the reason and how to resolve it (it leaves that code alone and just retains the original code without converting it)?
const translation = require('../translation.json');
My rollup config script "rollup.config.js" looks like this:
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import replace from '#rollup/plugin-replace';
import json from '#rollup/plugin-json';
export default {
input: 'src/javascript/script.js',
output: {
file: 'dist/script.js',
format: 'iife'
},
plugins: [
resolve(),
commonjs(),
replace({
'process.env.LANG': JSON.stringify(process.env.LANG)
}),
json()
]
};
My original intent was to resolve the require import with a dynamic variable that allows me to construct the file path in the following way:
const translation = require(`../translation_${process.env.LANG}.json`);
But I cannot even get the the simple require to resolve using Rollup
PS: I can get this to work with webpack but it creates so much code junk that it makes majority of the basic bundled code hard to read or in some cases unreadable, so trying to stay away from it
I currently have a large private NPM library which is being consumed by several other teams' apps across the business. At the moment the library is being published as one large single file (like the main lodash file) but this is causing application bundle size to be bloated as some of the applications don't need a large chunk of what is in the library.
So at the moment the apps are importing something like this
import { SomeReactComponent, someHelperFunction } from 'my-private-library';
What I want to achieve is the library published with individual modules similar to how Lodash, so the above would become:
import SomeReactComponent from 'my-private-library/lib/SomeReactComponent';
import someHelperFunction from 'my-private-library/lib/someHelperFunction';
I can get Webpack to output output the library in this format using multiple entry points, but what I can't get to work is getting Webpack to split out shared dependencies of each of those modules. So say the files look something like this:
src/SomeReactComponent.jsx
import React from 'react'
import SOME_CONST_STRING from '../constants';
const SomeReactComponent = () => {
return (
<div>You are using {SOME_CONST_STRING}</div>
);
}
export default SomeReactComponent;
src/someHelperFunction
import SOME_CONST_STRING from '../constants';
export default function someHelperFunction() {
return `This is just an example of ${SOME_CONST_STRING}`;
}
My Webpack is outputting the individual files, but it's not splitting out common code in a way that an app can consume the library. So notice above the SOME_CONST_STRING which is imported in each of the modules, Webpack is putting this code in both of the exported files.
My Webpack config looks a bit like this (removed other setting for brevity)
module.exports = {
entry: {
SomeReactComponent: './src/SomeReactComponent',
someHelperFunction: './src/someHelperFunction',
},
output: {
path: './lib',
library: 'MyPrivateLibrary'
libraryTarget: 'umd',
filename: '[name].js'
}
// removed other setting for brevity
}
I have tried using the splitChunks optimization setting like this
module.exports = {
entry: {
SomeReactComponent: './src/SomeReactComponent',
someHelperFunction: './src/someHelperFunction',
},
output: {
path: './lib',
library: 'MyPrivateLibrary'
libraryTarget: 'umd',
filename: '[name].js'
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
// removed other setting for brevity
}
which does chunk the code, but when I try to use the library in an app after doing this I get errors along the lines of (ERROR in TypeError: __webpack_require__(...) is not a function).
My question is can anyone see what I'm doing wrong? Is what I'm trying to achieve even possible with Webpack? Are there any example out there (as I can't find any) on how to do this?
Apologies for the example code, as my library is private I'm not able to use real-code examples.
Did you get success to achieve what you were trying to achieve in above scenario. I am working on the same use case and was facing similiar issue. After diagnosing it i found it that when we define library then in parsed module webpack add this in this object as window.myWebpackJsonpMyPrivateLibrary in minified main chunk which is undefined. if you remove the library and libraryTarget from webpack then you will not face this issue.
In my case i faced another issue that required chunk(s) are not being loaded when this is used as install dependency in another project.
I have a set of spec v1 custom elements which I'm using webpack 4 to bundle (and babel-loader to transpile).
The components all look similar to this:
export class CompDiv extends HTMLDivElement {
constructor(...args) {
const self = super(...args);
self.property = null;
return self;
}
connectedCallback() {
console.log('connected CompDiv');
}
}
customElements.define('comp-div', CompDiv, { extends: 'div' });
Now to be able to create custom packages from these components using selective, named imports I need to mark these files as side-effect-free.
The component registration, though, takes place in the module itself:
customElements.define('comp-div', CompDiv, { extends: 'div' });
As far as I understand, that is a sideeffect.
Now I have an index.js that basically looks like this:
export { CompDiv } from './components/comp-div/comp-div';
...
export { CompBtn } from './components/comp-btn/comp-btn';
My webpack entry point looks like this:
import 'document-register-element';
import 'babel-polyfill';
import { CompDiv } from './index';
Now when I do this, CompBtn (and all other exports in index.js) ends up being part of the bundle even though it's not imported in my webpack entry point.
What would be the recommended way of allowing for treeshaking in webpack with these web components?
From webpack guide - Mark the file as side-effect-free:
All the code noted above does not contain side effects, so we can simply mark the property as false to inform webpack that it can safely prune unused exports.
So, setting "sideEffects": false in package.json tells webpack that your modules are side effect free. So that it can prune unused exports (in your case, unused re-exports). This is generally used by library authors.
But that's just one side of the equation.
From webpack configuration docs - optimization.sideEffects:
Tells webpack to recognise the sideEffects flag in package.json or rules to skip over modules which are flagged to contain no side effects when exports are not used.
So, in order to leverage that previously mentioned option, the library consumer will have to set the optimization.sideEffects option to true in their webpack config file:
// webpack.config.js
module.exports = {
...
optimization: {
sideEffects: true
}
...
}
Note that, in production mode, this option is enabled by default. So, you'll only need to set it for development mode.
N.B.: In this case, you are both the author and the consumer of your modules.
Lastly, let's look at your webpack entrypoint:
// webpack entrypoint
import 'document-register-element';
import 'babel-polyfill';
import { CompDiv } from './index';
If you don't use your imported CompDiv later in this file, webpack will prune it - assuming you've set "sideEffects": false in package.json and optimization.sideEffects to true in your webpack config.
But, for example, even if you only imported 'babel-polyfill' and won't explicitly use anything from it later in this file, webpack will not prune it, because the package.json for babel-polyfill library doesn't contain "sideEffects": false.
I hope that clears things up.
everyone.
I have a trivial doubt on making vue components.
I don't want to use browserify or webpack , cause I am working in django and it has most of it's templates in static files , although I read this , which does describe how to take in account both ( but that's for some other day ).
Problem :
I am making a single file component which I have to import and use, using my router but I can't, as the import just doesn't happen.
My Hello.vue
<template>
Some HTML code here.
</template>
<script>
module.exports = {
data() {
return {
coin : []
}
},
beforeRouteEnter (to, from, next) {
axios.get('my-django-rest-api-url')
.then(response => {
next(vm => {
vm.data = response.data
})
})
}
}
</script>
I have it in the index.html file itself , no other .js file,
<script>
import Hello from '#/components/Hello.vue'
Vue.use(VueRouter);
const dashboard = {template:'<p>This is the base template</p>'};
const profile = {
template: '#profile_template',
data () {
return {
profile_details: []
}
},
beforeRouteEnter (to, from, next) {
axios.get('my-api-url')
.then(response => {
next(vm => {
vm.profile_details = response.data
})
})
}
}
const router = new VueRouter({
routes: [
{ path: '/', component: dashboard },
{ path: '/profile', component: profile },
{ path: '/hello', component: Hello }
]
});
new Vue({
router : router,
}).$mount('#app');
</script>
What all I've tried :
1.<script src="../components/Hello.js" type="module"></script> and removing the import statement as suggested here
Replacing my Hello.js's code with this : export const Hello = { ...
Making a Hello.js file and importing it like this import Hello from '../components/Hello.js';
Error :
**Mozilla ( Quantum 57.0.4 64 bit ) ** : SyntaxError: import declarations may only appear at top level of a module
**Chrome ( 63.0.3239.108 (Official Build) (64-bit) ) ** :Uncaught SyntaxError: Unexpected identifier
P.S. : I have tried these in various combinations
Not a Vue.js guru, but here are a few perspectives that might help you.
Module loading is still not supported on modern browsers by default, and you'd need to set special flags in order to enable it (which the users of your app probably won't do).
If you insist on using import and export, you'd need Webpack. And most certainly Babel (or any other ES6 transpiler, e.g. Buble) as well.
If you prefer module.exports, then you'd need Browserify. It enables support for CommonJS in browser environments.
If neither is doable, then your best bet is defining Vue components in global scope. You can split them across separate files, and import each with a <script> individually. Definitely not the cleanest approach.
Single file components typically go inside of .vue files, but either way they require vue-loader which can be added and configured (again) with a bundler.
Last option is to just use an existing setup in place, if there is any (is there?). If you already have RequireJS, UMD, or something similar in place, adjust your components to fit that. Otherwise, use <script>s.
You are trying to do something which is not possible. Vue Single file components are not supported as raw component file by web browsers. The single file component is supposed to be compiled.
Please see this for more:
https://v2.vuejs.org/v2/guide/single-file-components.html
In Webpack, each file can be transformed by a “loader” before being included in the bundle, and Vue offers the vue-loader plugin to translate single-file (.vue) components.
A vue single file component is first "translated" (compiled) to pure javascript code which is use-able by browsers.