Using createjs-soundjs in react web app - javascript

I would like to use https://www.npmjs.com/package/createjs-soundjs to play sounds on a react web app.
I installed the package normally with npm install createjs-soundjs and it's showing in the packages list. How should I include this to my project, so that I could use it?
I have tried the following:
import React from 'react';
import classNames from 'classnames';
import createjs from 'createjs';
import SoundJS from 'createjs-soundjs'; // this line causes an error
The error message in console: soundjs-0.6.2.min.js:17 Uncaught ReferenceError: createjs is not defined. This error message is very clear, but I have no idea where to start. Am I missing something fundamental?

The SoundJS core bundle is not in the proper CommonJS or ES6 format, it's a bundle that makes strong assumption that it's loaded as a top-level script in the browser. So it first tries to create the global value by assigning it to a property on this (which it assumes to be the window) and then tries to access this global values as just createjs.
So obviously this doesn't work in the module context.
There's a workaround though, the code is for Webpack 2 and based on this comment (which is for Webpack 1):
module: {
rules: [
// ...
{
test: /createjs/,
use: [
'imports-loader?this=>window',
'exports-loader?createjs'
]
},
// ...
]
},
plugins: [
// ...
new webpack.ProvidePlugin({
'createjs': 'createjs',
}),
// ...
],
resolve: {
alias: {
'createjs': path.resolve(__dirname, '../lib/soundjs-0.6.2.combined'),
}
},
The 3rd entry (resolve.alias) maps the imports of createjs to the file I've downloaded and placed into my lib folder (which is not as elegant as using something from npm, but now I'm sure what I get. npm version might work as well).
The first entry (module.rules) tells Webpack to first substitute this with window, and then to take the createjs instance from the global (window) context and make it the module export.
Finally, the 2nd entry (ProvidePlugin) precedes all requests for global createjs (in other modules) with const createjs = require('createjs').

Related

How can I change in Rollup.js an import module of a dependency package to a local file replacing that module?

I have a JavaScript project that must be bundled using Rollup.js which depends on a package A which in turn depends on a package B:
"mypackage" ---import--> "A" ----import----> "B"
Let's say that my package import a function "connect" from package A, which in turn import a "connect" function exported by the module B. Something like:
//mypackage index.js
import { connect } from 'A'
//A index.js
import { connect } from 'B'
//B index.js
export function connect() {}
Since my package requires a bundled version of the package B (let's say "B.bundle.js"), how can i configure Rollup.js in order to replace for each dependency of my project requiring B (A in this case) to use my local bundled version (i.e. B.bundle.js, which of course exports the "connect" function too)?
When Rollup.js creates the bundled version of my project i would like to achieve something like the following:
//A index.js after being processed by Rollup
import { connect } from './src/B.bundle.js'
Is something like this possible with Rollup or with a plugin? Sorry for the question, but I'm new to rollup and bundling in general.
I solved this issue using some configuration of my package package.json and the rollup plugin #rollup/plugin-node-resolve.
In the package.json of my package I inserted the browser option that specifies how modules should be resolved when my package is used in the browser context. From the npm doc on the browser option of the package.json:
If your module is meant to be used client-side the browser field should be used instead of the main field. This is helpful to hint users that it might rely on primitives that aren't available in Node.js modules. (e.g. window)
So considering the example provided in the original question the npm package contains something like this:
{
"name": "mypackage",
"version": "1.5.1",
"description": "A brand new package",
"main": "index.js",
"browser": {
"B": "./B.bundle.js"
},
}
This means that when mypackage is used in the context of the browser the module B import will load from the file located in "./B.bundle.js".
Now, with rollup i need to specify that the bundle i am creating is intended for browser context. The plugin that handle imports of node modules is #rollup/plugin-node-resolve. There is an option is this plugin that specify that the context is browser. From the plugin documentation about the option browser:
If true, instructs the plugin to use the browser module resolutions in package.json and adds 'browser' to exportConditions if it is not present so browser conditionals in exports are applied. If false, any browser properties in package files will be ignored. Alternatively, a value of 'browser' can be added to both the mainFields and exportConditions options, however this option takes precedence over mainFields.
So that in my rollup config file i have something like:
// rollup.config.js
import commonjs from "#rollup/plugin-commonjs";
import resolve from "#rollup/plugin-node-resolve";
import nodePolyfills from "rollup-plugin-node-polyfills";
export default {
input: "index.js",
output: {
file: "dist/mypackage.bundle.js",
format: "es",
},
plugins: [
nodePolyfills(),
resolve({
browser: true, //<- tells to rollup to use browser module resolution
}),
commonjs(),
],
},
The answer of #vrajpaljhala seems feasable for the purpose but imho #rollup/plugin-replace its kind of too tricky and raw approach to the problem because it involves direct replacmeent of strings surrounded by "". We may face some very "hard to discover" errors if the package name we are going to replace is a common word that can be present as a string in the code but not in the context of an import statement.
We had the exact same requirement and we resolved it using #rollup/plugin-replace package.
Basically, our project uses a monorepo structure but we are not using any tools like learna or workspace for managing it. So we have two packages which are interdependent. Our ui-kit uses icons package for different icons and icons package uses the Icon component from ui-kit package. We just replaced the imports for ui-kit with local path with the following:
import replace from '#rollup/plugin-replace';
...
...
export default {
...
...
plugins: [
replace({
'ui-kit': JSON.stringify('../../../../src/components/Icon'),
delimiters: ['"', '"'],
}),
]
...
...
}

Imported dependency requires another dependency to be first available in scope

I'm trying to import a library that extends another one, here's my first approach:
import * as PIXI from 'pixi.js';
import 'pixi-layers'; // extends PIXI classes
I get an Uncaught ReferenceError: PIXI is not defined on runtime when pixi-layers tries to Object.assign on a PIXI class.
I suppose this is related to how webpack's hoisting, I've tried to use:
imports-loader
script-loader
I've also tried to provide PIXI through webpack:
module.exports = {
// ...
plugins: [
new webpack.ProvidePlugin({
PIXI: 'pixi.js',
}),
],
};
PIXI remains unavailable to pixi-layers, how can I bundle pixi-layers and make sure it can actually work with the PIXI object?
Turns out providing PIXI as a plugin through the webpack config file was the solution, I just has this same config file compile in the wrong location (paths are hard).

Dynamic loading of external modules in webpack fails

I am trying to set up the following architecture: a core React application that gets built with some basic functionality, and the ability to load additional React components at runtime. These additional React components can be loaded on-demand, and they are not available at build time for the core application (so they cannot be included in the bundles for the core application, and must be built separately). After researching for some time, I came across Webpack Externals, which seemed like a good fit. I am now building my modules separately using the following webpack.config.js:
const path = require('path');
const fs = require('fs');
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
module.exports = {
entry: './src/MyModule.jsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'MyModule.js',
library: 'MyModule',
libraryTarget: 'umd'
},
externals: {
"react": "react",
"semantic-ui-react": "semantic-ui-react"
},
module: {
rules: [
{
test: /\.(js|jsx|mjs)$/,
include: resolveApp('src'),
loader: require.resolve('babel-loader'),
options: {
compact: true,
},
}
]
},
resolve: {
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx']
}
};
Took a look at the generated MyModule.js file, and it looks correct to me.
Now, in my core app, I am importing the module as follows:
let myComponent = React.lazy(() => import(componentName + '.js'));
where componentName is the variable that matches the name of my module, in this case, "MyModule" The name is not known at build time, and the file is not present in the src folder at build time. To avoid errors from webpack when building this code with an unknown import, I have added the following to my webpack.config.js for the core project:
module.exports = {
externals: function (context, request, callback/*(err, result)*/) {
if (request === './MyModule.js') {
callback(null, "umd " + request);
} else {
callback();
}
}
}
I have confirmed that the function in externals gets called during the build, and the if condition is matched for this module. The build succeeds, and I am able to run my core application.
Then, to test dynamic loading, I drop MyModule.js into the static/js folder where the bundles for my core app live, then I navigate to the page in my core app that requests MyModule via let myComponent = React.lazy(() => import(componentName + '.js'));
I see a runtime error in the console on the import line,
TypeError: undefined is not a function
at Array.map (<anonymous>)
at webpackAsyncContext
My guess is it's failing to find the module. I don't understand where it is looking for the module, or how to get more information to debug.
Turns out that I was making a couple of incorrect assumptions about webpack and dynamic loading.
I was having issues with two things - the kind of module I was loading, and the way that I was loading it.
Dynamic importing is not yet a standard ES feature - it is due to be standardized in ES 2020. This dynamic import will only return a module if the module object you are attempting to load is an ES6 module (aka something that contains an 'export ModuleName'). If you attempt to load something packed up as a CommonJS module, AMD, UMD, the import will succeed, but you will get an empty object. Webpack does not appear to support creating bundles in ES6 format - it can create a variety of module types, and in my config file above, I was actually creating UMD modules (configured via libraryTarget setting).
I had issues with the import statement itself because I was using it within an app bundled by Webpack. Webpack reinterprets the standard ES import statement. Within a standard webpack config (including the one you get from CRA), webpack uses this statement as a split point for bundles, so even modules that are dynamically imported are expected to be there at webpack build time (and the build process will fail if they are not available). I had tried to use webpack externals to tell webpack to load the modules dynamically, which allowed the build to succeed without the modules being there. However, the app still used Webpack's import function instead of the standard JS import function at runtime. I confirmed this by attempting to run import('modulename') from the browser console and getting a different result than my app, which was bundled with webpack.
To solve problem #2, you can tell Webpack to not reinterpret the ES dynamic import by adding some annotation to the import statement.
import(/*webpackIgnore: true*/ 'path/to/module.js');
This will both prevent Webpack from attempting to find and bundle the dynamically imported module at build time, and attempting to import it at runtime. This will make behavior in the app match behavior in the browser console.
Problem #1 was a bit more difficult to solve. As I mentioned above, importing a non-ES6 module will return an empty object (if you await the promise or use .then()). However, as it turns out, the file itself does load and the code gets executed. You can export the module in the "window" format using Webpack, and then load it as follows.
await import(/*webpackIgnore: true*/`path/to/module.js`);
let myModule = window['module'].default;
Another potential solution that avoids using the window object is building the module using a system capable of producing ES6 modules (so, not Webpack). I ended up using Rollup to create an ES6 module that pulled all dependencies into a single file, and ran the output through Babel. This produced a module that loaded successfully via a dynamic ES import. The following was my rollup.config.js (note that I included all external node modules needed in my module - this bloated the module size but is a requirement for my specific application - yours will likely differ and you will need to configure rollup to exclude the modules)
// node-resolve will resolve all the node dependencies
import commonjs from 'rollup-plugin-commonjs';
import resolve from 'rollup-plugin-node-resolve';
import babel from 'rollup-plugin-babel';
import replace from 'rollup-plugin-replace';
export default {
input: 'src/myModule.jsx',
output: {
file: 'dist/bundle.js',
format: 'esm'
},
plugins: [
resolve(),
babel({
exclude: 'node_modules/**'
}),
commonjs({
include: 'node_modules/**',
namedExports: {
'node_modules/react/index.js': ['Children', 'Component', 'PropTypes', 'PureComponent', 'React', 'createElement', 'createRef', 'isValidElement', 'cloneElement', 'Fragment'],
'node_modules/react-dom/index.js': ['render', 'createElement', 'findDOMNode', 'createPortal'],
'node_modules/react-is/index.js': ['isForwardRef']
}
}),
replace({
'process.env.NODE_ENV': JSON.stringify( 'production' )
})
]
}

Using Vue Design System in Nuxt is throwing errors about export in system.js

I am trying to get the components imported into a Nuxt project, following the steps here:
https://github.com/viljamis/vue-design-system/wiki/getting-started#using-design-system-as-an-npm-module
Nuxt does not have a main.js (everything is plugin based), so what I have done is create a "plugin" and then do the import code in there like so (Nuxt recommends this for other libraries too and works fine):
vue-design-system.js
import Vue from 'vue'
import system from 'fp-design-system'
import 'fp-design-system/dist/system/system.css'
Vue.use(system)
Then in my config I do (removed other code in config):
nuxt.config.js
module.exports = {
css: [
{ src: 'fp-design-system/dist/system/system.css', lang: 'css' }
],
plugins: [
{ src: '~plugins/vue-design-system', ssr: true }
]
}
When I run npm run dev in my theme, it builds, but I get a warning:
WARNING Compiled with 1 warnings warning in
./plugins/vue-design-system.js 7:8-14 "export 'default' (imported as
'system') was not found in 'fp-design-system'
Seems to have an issue with the generated system.js regarding the export (the command npm run build:system).
In my page on screen I get the following error when trying to use a component in the design system:
NuxtServerError Cannot find module
'fp-design-system/src/elements/TextStyle' from
'/Users/paranoidandroid/Documents/websites/Nuxt-SSR'
If I hard refresh the page, I then get another message:
NuxtServerError render function or template not defined in component:
anonymous
Any idea what's happening here? It would be really great to get this working somehow.
At this current time, I'm not sure if it's a Nuxt issue or a Vue Design System issue. I think the latter, just because the Nuxt setup I have right now is very bare-bones...so it's not something else causing this.
Thanks.
Repository on GitHub:
Here is the repo for my "theme", but in order to get this going, you will need to create a design system separate from this with the same name and follow the steps to use the design system as a local (file) NPM module.
https://github.com/michaelpumo/Nuxt-SSR
console.log of system (from the JS import statement)
As for your first error (""export 'default' (imported as 'system') was not found in 'fp-design-system'"), the UMD built JS from vue-design-system does not export a "default" object. But you can simply workaround the issue by importing it as:
import * as system from 'fp-design-system'
instead of:
import system from 'fp-design-system'
Then another issue comes quickly as you noticed in your comments: "window is not defined", due again to the UMD built JS that expects window to be globally available, instead of the usual trick to use this (which equals window in a browser). Therefore as it is, the build is not comptible with SSR.
You could however slightly rework the built JS by replacing the first occurrence of window by this, but I am not sure if the result will still work.
Most probably you should better keep this module for client rendering only.
It seems Vue is looking for the ES6 pattern for importing module, which you should use for external javascript modules/files.
in ES6 it is
export default myModule
in ES5 it was
module.exports = myModule
Hope it will help.

Testing TypeScript modules with package dependencies

I have a class written in TypeScript:
import * as uuid from "uuid";
export class IdGenerator {
getId() {
return uuid.v4();
}
}
This has a dependency on the uuid package that I have installed with npm.
I deploy this with other code written in TypeScript to the browser using webpack. Dependencies are resolved, all is well.
I want to test the class, so I write the following test:
import { IdGenerator } from "../src/IdGenerator";
describe("An id generator", () => {
const idGenerator = new IdGenerator();
it("generates an id", () => {
expect(idGenerator.getId()).not.toBeNull();
});
});
I use Chutzpah with the following configuration:
{
"Framework": "jasmine",
"TestHarnessReferenceMode": "AMD",
"TestHarnessLocationMode": "SettingsFileAdjacent",
"EnableTestFileBatching": true,
"References": [
{
"Path": "node_modules/requirejs/require.js",
"IsTestFrameworkFile": true
}
],
"Compile": {
"Mode": "External",
"Extensions": [ ".ts" ],
"ExtensionsWithNoOutput": [ ".d.ts" ]
},
"Tests": [
{ "Path": "Components/test" }
]
}
When trying to test with Chutzpah, I get the following error:
Unhandled exception [...] in [...] /node_modules/requirejs/require.js [...] Script error for "uuid", needed by: Components/src/IdGenerator
So I add a reference to the path in chutzpah.json, which gets rid of this error but raises an error for one of uuid's dependencies. I add a reference for the dependency, then get a further dependency error, and so on.
Ideally I would like all dependencies to be resolved in the same way as they are with the bundle deployed to the browser.
Should I abandon the idea of trying to test my TypeScript files with package dependencies in this way and instead look into using dependency injection and mock the package dependencies in the tested TypeScript files? Perhaps also create separate JavaScript tests for the bundle? Or is there another approach that will allow the testing of my TypeScript code with the package dependencies?
This looks like it won't work since requireJS and Node are not compatible in this way. See here. From that answer:
It is not possible to use RequireJS on the client (browser) to access files from node_modules. Files under node_modules must first be copied to a location that is accessible (under the public folder) before the client can access them. The documentation says RequireJS can access Node modules, but this only works for server-side JavaScript (when you want to use RequireJS-style module syntax in Node).
To use your config module in the client app, you must first transform it into a RequireJS-compatible module and copy it under public. This article explains how to automate this with package managers and build tools, and contains the quote that finally fixed my broken understanding of RequireJS + Node:
In order to use node modules on the browser you will need a build
tool. This might bother you. If this is a deal breaker then by all
means continue wasting your life copying and pasting code and
arranging your script tags.

Categories

Resources