Three.js specific imports failing, possibly Webpack's fault - javascript

Disclaimer: I'm using create-react-app and three.js v0.99.0.
I'm trying to import specific three.js modules, since importing straight from the root module includes the entire library in the bundle, which is 0.5MB uncompressed. Most direct src/ imports work fine, however when importing Geometry, AKA changing this:
import { Geometry } from "three";
to this:
import { Geometry } from "three/src/core/Geometry";
The line on my graph that it was drawing no longer appears, and there's no error messages. In addition, importing the WebGLRendered straight from src/ caused the whole thing to implode:
import { WebGLRenderer } from "three"; // from this
import { WebGLRenderer } from "three/src/renderers/WebGLRenderer"; // to this
with the error:
WebGLIndexedBufferRenderer.js:14 Uncaught TypeError: Cannot read property 'type' of undefined
at WebGLIndexedBufferRenderer.setIndex (WebGLIndexedBufferRenderer.js:14)
at WebGLRenderer.renderBufferDirect (WebGLRenderer.js:505)
at renderObject (WebGLRenderer.js:932)
at renderObjects (WebGLRenderer.js:913)
at WebGLRenderer.render (WebGLRenderer.js:790)
at SceneManager.js:75
...
I checked the definitions of these modules in the three.js library, and they are literally copy pasted between three.module.js and src/, so there's no code differences I can find. However, one thing I did notice is that if I import both and print them, Webpack seems to be transpiling the one I import from src/:
ƒ Geometry() {
Object.defineProperty(this, 'id', {
value: geometryId += 2
});
this.uuid = _math_Math_js__WEBPACK_IMPORTED_MODULE_10__["_Math"].generateUUID();
...
as opposed to:
ƒ Geometry() {
Object.defineProperty(this, 'id', {
value: geometryId += 2
});
this.uuid = _Math.generateUUID();
...
is it possible that create-react-app's Webpack is messing with imports that are within a src/ folder, thus making them not work despite the source code being identical? If so, is there a way to make it only transpile code within MY src/ folder, and not the 3rd partiy modules'?

It's probably a babel issue. By default, babel won't transpile anything in node_modules, however you're importing from the ES6 source, so you need to force it to transpile these. In your webpack config, you should have a rule (in module.rules) for js(x) files that looks something like
{
test: /\.jsx?$/,
use: 'babel-loader'
}
add a custom exclude that will exclude everything in node_modules except the three files, i.e.
{
test: /\.jsx?$/,
exclude: /node_modules\/(?!three)/,
use: 'babel-loader'
}

Related

Tailor dependencies to ES or CJS with Rollup

I have a NPM package (private) which works in both a browser and Node environment.
This is done by creating separate bundles via Rollup for ES and CJS, so the output looks like:
dist/ejs/index.js // Import this for your browswer environments
dist/cjs/index.js // Use this for Node environments
Pretty standard. Now I'm adding a dependency to this, which follows the same bundling pattern.
I can import the library like so:
import { externalLibrary } from "#external/ejs/externalLibrary";
All is good in a browser environment. But now this does not work in a Node environment, as what I'm importing is not CJS.
I could change the way I import the library to require and target the cjs bundle:
const { externalLibrary } = require("#external/cjs/externalLibrary");
And while this works in both environments, I don't think it's optimal.
Is there a better way of doing this? Some configuration that I could specify when exporting the CJS bundle?
module.exports = {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
// Behaviour here for #external/cjs/externalLibrary ?
}
};
I overlooked the package.json config. You can specify different entry files depending on the build here:
{
...
"main": "dist/cjs/index.js",
"module": "dist/ejs/index.js",
...
}
Then I removed the implicit import of the EJS file, and targeted just the package:
// Before:
import { externalLibrary } from "#external/dist/ejs/externalLibrary";
// After:
import { externalLibrary } from "#external";
This then ensures either the CJS or ES build is used, depending on the environment using the package.
Looks like you already found the solution for this, but even with old import style
import { externalLibrary } from "#external/dist/ejs/externalLibrary";
you should be able to target appropriate formats for cjs vs esm. With rollup, you would have to configure the output config to be an array of objects with appropriate format set. For example:
module.exports = {
input: 'src/main.js',
output: [{ file: 'dist/index.cjs.js', format: 'cjs' },
{ file: 'dist/index.esm.js', format: 'es' }],
}
Also, being an author of klap, I would recommend giving it a try as it would bring in lot of other optimizations by default.

How to add a folder as entry in npm package?

I am trying to publish a npm module. Which has a following folder structure.
In my package.json it has "main": "./dist/" I understand this resolve for index.js. But in the dist folder I have individual files named as string.js, class.js, dom.js I am planning to import them as
import { isValidZipCode } from '#scope/utils/string'; but right now I have to import them as import { isValidZipCode } from '#scope/utils/dist/string';
Is there a way I can resolve a folder when I import a module from node_modules?
EDIT: Main idea is to import the files as import { isValidZipCode } from '#scope/utils/string' when I keep individual files for individual exports.
The other answers are correct for the most part, but I think there's one thing that's missing (either from your OG post or from their answers), which is:
Your folder structure is definitely not standard, which likely led to your current problems as well as non-helpful results in the Google searches when you tried to find an answer.
You didn't show your package.json nor your webpack.config.js file contents, which are the key to answering your question even if you did have such a weird file structure.
Some suggestions:
Change your folder structure to be something along the lines of
/
|--src
|--utils
|--string.js
|--[... other js files]
|--index.js
|--dist (will be generated automatically)
|--[config files, like package.json, webpack.config.js, etc]
Make your webpack.config.js have something along the lines of:
output: {
path: path.resolve(__dirname, 'dist'),
//...
}
plugins: [
new CopyWebpackPlugin({
patterns: [
'ReadMe.md', // optional
'package.json',
'LICENSE.md' // optional
]
})
],
In order to fix/normalize the output (e.g. output would be /dist/utils/[string.js, ...], /dist/package.json).
Then, make your package.json main something like
"main": "utils/string.js"
After doing that, your output should look something like
/
|--src
|--utils
|--string.js
|--[... other js files]
|--index.js
|--dist
|--utils
|--string.js
|--[... other js files]
|--index.js // optional: only if you want to support stuff like
// `import { isValidZip } from '#scope/utils';`
|--package.json
|--[config files, like package.json, webpack.config.js, etc]
Finally, you need to cd dist and run npm publish from inside there. (That's why you need the package.json inside that directory.)
I can't really go into details about the #scope portion since I haven't done that myself, but I did the above for one of my own projects and it worked as expected.
All you need to do is to make a index file in root folder then just export all files with the following:
In your dist/string export each method/function on it, and for the index do it follows:
export * from "./dist";
as it helps maintain code and looks cleaner to eye
Regards :)
Create a index file in root folder then just export all files like this
export { default as Str } from "./dist/string";
export { default as Cls } from "./dist/class";
export { default as Dom } from "./dist/dom";
and also update package.json change main from./dis/ to ./
Hope this will help you. Happy coding.

three.js ES6 how import only specific modules

I've installed three.js library through NPM to get advantage of the new ES6 modular architecture which should let you to import just the modules you need, as explained here: Threejs - Import via modules.
I am using gulp, browserify and babel for bundling and transpiling, like so:
gulp.task("build_js", () => {
return browserify({
entries: "./public/app/app.js",
cache: {},
dev: true
})
.transform(babelify, {presets: ["env"], plugins: ["syntax-async-functions", "transform-async-to-generator"], sourceMaps: true})
.bundle()
.pipe(source("app.bundle.min.js"))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: mode}))
.pipe(uglify())
.pipe(sourcemaps.write("./"))
.pipe(gulp.dest(config.build.js))
});
I want to import only the modules I need and keep the bundle size small, but I noticed that the bundle generated by browserify has the same size regardless if I import all the modules or just one.
If in app.js I import all the modules I got a bundle size of about 500Kb:
// app.js
import * as THREE from 'three'; // about 500 Kb size
But if I try to import just a specific module using ES6 syntax I got the same bundle size (it is importing again all the modules):
// app.js
import { Vector3 } from 'three'; // about 500 Kb size, same as before example
I've also tried the following:
// app.js
import { Vector3 } from "three/build/three.module.js";
But I got the following error:
SyntaxError: 'import' and 'export' may only appear at the top level (45590:0) while parsing /Users/revy/proj001/node_modules/three/build/three.module.js
My question: how can I properly import only the modules I need and keep the bundle size small?
You are missing the concept of Tree Shaking.
When you import a modules by name the other modules are not automatically removed from the bundle. The bundler always includes every module in the code and ignores what you have specified as import names.
The other unused modules, which you did not import, are considered dead code because they are in the bundle however they are not called by your code.
So to remove this unused code from the bundle and thus make the bundle smaller you need a minifier that supports dead code removal.
Check out this popular tree shaking plugin for browserify - it should get you started:
https://github.com/browserify/common-shakeify
Solved using rollupify inside browserify transform. It will perform tree shaking and remove dead code:
gulp.task("build_js", () => {
return browserify({
entries: "./public/app/app.js",
cache: {},
dev: true
})
.transform(rollupify, {config: {}}) // <---
.transform(babelify, {presets: ["env"], plugins: ["syntax-async-functions", "transform-async-to-generator"], sourceMaps: true})
.bundle()
.pipe(source("app.bundle.min.js"))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: mode}))
.pipe(uglify())
.pipe(sourcemaps.write("./"))
.pipe(gulp.dest(config.build.js))
});
}
Still I would appreciated an explanation on why ES6 module import works like this..

Foundation 6.4 JavaScript within project not working, but external JS does

I am losing my mind with javascript in Foundation 6.4. I have no idea what's going on with this Webpack thing. It seems like some libraries/plugins work and some do not. My latest issue is with plyr (https://plyr.io/). I do not understand why TweenMax works 100% fine and plyr.js does not. What am I doing wrong?
This is the error I get..
app.js:23026 Uncaught ReferenceError: plyr is not defined
This is what my app.js looks like..
import $ from 'jquery';
import whatInput from 'what-input';
window.$ = $;
window.jQuery = $;
require('./TweenMax.js');
require('./plyr.js');
//import Foundation from 'foundation-sites';
// If you want to pick and choose which modules to include, comment out the above and uncomment
// the line below
import './lib/foundation-explicit-pieces';
$(document).foundation().ready(function(){
TweenMax.set(".logo-center", {transformOrigin:"50% 50%"});
var blast = plyr.setup('#blast', {
hideControls: true,
clickToPlay: false,
controls: []
});
});
I also have the path to plyr.js in my config.yml file:
# Your project's server will run on localhost:xxxx at this port
PORT: 8000
# Autoprefixer will make sure your CSS works with these browsers
COMPATIBILITY:
- "last 2 versions"
- "ie >= 10"
- "ios >= 9"
# UnCSS will use these settings
UNCSS_OPTIONS:
html:
- "src/**/*.html"
ignore:
- !!js/regexp .foundation-mq
- !!js/regexp ^\.is-.*
# Gulp will reference these paths when it copies files
PATHS:
# Path to dist folder
dist: "dist"
# Paths to static assets that aren't images, CSS, or JavaScript
assets:
- "src/assets/**/*"
- "!src/assets/{img,js,scss}/**/*"
# Paths to Sass libraries, which can then be loaded with #import
sass:
- "node_modules/foundation-sites/scss"
- "node_modules/motion-ui/src"
# Paths to JavaScript entry points for webpack to bundle modules
entries:
- "src/assets/js/app.js"
- "src/assets/js/plyr.js"
- "src/assets/js/TweenMax.js"
I assume you started your Foundation project from ZURB template. It uses a Gulpfile (gulpfile.babel.js) for JavaScript module bundling with Webpack.
Inside this script there is a Webpack configuration like below:
let webpackConfig = {
module: {
rules: [
{
test: /.js$/,
use: [
{
loader: 'babel-loader'
}
]
}
]
}
}
Webpack config can define, in the module section, some rules for modules (configure loaders, parser options, etc.).
In particular loaders enable webpack to process files and bundle them (or do other Webpack tasks).
So the specific configuration in gulpfile.babel.js tells to Webpack to use babel-loader for all files in /src folder with js extension.
However as explained in Webpack - Shimming some modules/libraries may expect global dependencies (e.g. $ for jQuery) or create globals which need to be exported.
To support these libraries, like Plyr, you can use expose-loader.
So add a module rule to Webpack config inside
gulpfile.babel.js, pointing to expose-loader for plyr.js:
let webpackConfig = {
module: {
rules: [
{
test: /plyr.js/,
use: [
{
loader: 'expose-loader',
options: 'plyr'
}
]
},
{
test: /.js$/,
use: [
{
loader: 'babel-loader'
}
]
}
]
}
}
No other changes are needed to your project files (config.yml, app.js).
This way you should access plyr as global variable like in your app.js:
var blast = plyr.setup('#blast', {
hideControls: true,
clickToPlay: false,
controls: []
});
You might need to use something like expose-loader so that plyr is available globally on window. Here's more info on how we used expose-loader in our project.
Without the actual project code, it is rather difficult to figure out what went wrong. But here's my take on the situation.
First off, why are you import plyr from a local file? Shouldn't it be require('plyr') instead of require('./plyr.js').
Also, why are you using plyr with a lower-case 'p' when the module exports the library as Plyr, with an upper-case 'P' in global binding, i.e., binding on window.
Your code:
plyr.setup('#blast', {
...
});
The plyr module:
! function (e, t) {
"object" == typeof exports && "undefined" != typeof module
? module.exports = t() // commonjs
: "function" == typeof define && define.amd
? define("Plyr", t) // requirejs
: e.Plyr = t() // window/global binding
}(this, function () {
...
});
Full dump here and here. Source here.
You can make it work by loading plyr.js in your index.html before loading your code and use the globally bound Plyr.
// index.html
<html>
<head>
<script src="plyr.js"></script> // window.Plyr = ...
</head>
<body>
...
<script src="bundle.js"></script>
</body>
</html>
//main.js
...
Plyr.setup('#blast', {
hideControls: true,
clickToPlay: false,
controls: []
});
You could also import plyr module in your code as a named import:
import Plyr from 'plyr';
...
Plyr.setup('#blast', {
hideControls: true,
clickToPlay: false,
controls: []
});
If all this still don't work, I believe it'd best if you share your project code - at least something more than you did alreday.

Webpack missing modules with CommonsChunk and extract-text-webpack-plugin

I'm following Maxime Fabre's tutorial on Webpack and am trying to get a really simple webpack bundle with 1 entry point and 2 chunks to work. Since both chunks require jquery and mustache, I'm using CommonsChunkPlugin to move the common dependencies up to the main bundle file, just like in the tutorial. I'm also using extract-text-webpack-plugin to extract styles from the chunks and put them in a separate CSS file.
My webpack.config.js:
var ExtractPlugin = require("extract-text-webpack-plugin");
var plugins = [
new ExtractPlugin("bundle.css"),
new webpack.optimize.CommonsChunkPlugin({
name: "vendors", //move dependencies to our main bundle file
children: true, //look for dependencies in all children
minChunks: 2 //how many times a dependency must come up before being extracted
})
];
module.exports = {
/*...*/
entry: "./src/index.js",
output: {
/*...*/
},
plugins: plugins,
module: {
loaders: [
/*...*/
{
test: /\.scss$/,
loader: ExtractPlugin.extract("style", "css!sass")
//loaders: ["style", "css", "sass"]
},
/*...*/
]
}
};
Relevant code in the entry point (I'm using ES6 syntax and babel):
import "./styles.scss";
/*if something is in the page*/
require.ensure([], () => {
new (require("./Components/Chunk1").default)().render();
});
/*if something else is in the page*/
require.ensure([], () => {
new (require("./Components/Chunk2").default)().render();
});
Both chunk1 and chunk2 look something like this:
import $ from "jquery";
import Mustache from "mustache";
/*import chunk templates and scss*/
export default class /*Chunk1or2*/ {
render() {
$(/*some stuff*/).html(Mustache.render(/*some other stuff*/));
}
}
index.html:
<html>
<head>
<link rel="stylesheet href="build/bundle.css">
</head>
<body>
<script src="/build/main.js"></script>
</body>
</html>
When I run webpack the bundle builds just fine. However, in the browser I get a Uncaught TypeError: Cannot read property 'call' of undefined, and on closer inspection it looks like several modules end up as undefined in the final bundle.
My bug looks a lot like https://github.com/wenbing/webpack-extract-text-commons-chunk-bug. When I disable either extract-text-webpack-plugin or CommonsChunkPlugin and build it the webpack bundle works beautifully.
However even though I'm following a simple tutorial with 2 very common plugins the bug seems rare, so I'm assuming I'm messing up somewhere. What gives?

Categories