How to dynamically load modules in Babel and Webpack? - ecmascript-6

I'm trying to use the dynamic module loading feature in ES6 and it seems that it's not actually implemented yet. But there are substitutes like ES6 Module Loader Polyfill which supposedly should do the trick for the time being.
So I have a ES6 project transpiled to ES5 using Babel and Webpack, and it works fine on its own. But all of my code is merged into one bundle.js file which I would like to split into modules. And when I tried the mentioned Polyfill, it throws some error from within and the project won't even start.
index.js:6 Uncaught TypeError: Cannot read property 'match' of undefined
And line 6 reads:
var filePrefix = 'file:' + (process.platform.match(/^win/) ? '/' : '') + '//';
Here's my package.js file:
{
"dependencies": {
"es6-module-loader": "^0.17.11",
"events": "^1.1.0",
"flux": "^2.1.1",
"fs": "0.0.2",
"react": "^15.0.2",
"react-addons-css-transition-group": "^15.0.2",
"react-dom": "^15.0.2",
"react-router": "^2.4.0",
"react-tap-event-plugin": "^1.0.0",
},
"devDependencies": {
"babel-core": "^6.8.0",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"html-webpack-plugin": "^2.16.1",
"react-hot-loader": "^1.3.0",
"transfer-webpack-plugin": "^0.1.4",
"webpack": "^1.13.0",
}
}
Can someone please point me to a working example of dynamic module loading with Webpack and Babel?

There's really three things at play here... dynamic importing, lazy loading, and code splitting/bundling. The System.import method, poly-filled with ES6 Module Loader will allow dynamic imports but not dynamic code splitting:
However, most transpilers do not support converting System.load calls to require.ensure so you have to do that directly if you want to make use of dynamic code splitting.
Dynamic code splitting is when you create child bundles within an entry point, which you can then dynamically lazy load. For this I would recommend using the promise-loader which is a bit more friendly than require.ensure:
import LoadEditor from 'promise?global,[name]!./editor.js';
...
if (page === 'editor') {
LoadEditor().then(Editor => {
// Use the Editor bundle...
})
}
Webpack will now break the editor.js module and all of it's dependencies into a separate bundle which can be loaded immediately or dynamically (as shown above). Lastly, depending on the size of the app, I think you should also consider splitting the vendor code out.
UPDATE
System.import was removed from the spec and replace by just import(). The new webpack docs have a page that discusses dynamic imports in webpack and the limitations of them.

Related

Webpack not loading JSON into Javascript

I'm trying to load a dynamically generated JSON file into my javascript app - but I keep running into issues.
NOTE: I'm not using Node.js, just straight up vanilla javascript.
Everything I read said to do this with Webpack and their website clearly states that: beginning with Webpack version 2.0.0 json loaders are no longer needed.
Well right now I'm trying the following in my app.js file and getting an error:
import * as contractArtifact from "../build/contracts/MySmartContract.json"
The error is:
import declarations may only appear at top level of a module
NOTE: that MySmartContract.json file that I'm trying to load is not static; it's dynamically generated and has to be loaded anew every time.
I'm very new to webpack but I'm pretty sure I've installed every single package that I might need for this - according to the million tutorials I've gone through.
Here's what's in my package.json file right now:
...
"scripts": {
"serve": "webpack && node server.js"
},
"devDependencies": {
"#babel/template": "^7.10.4",
"file-loader": "^6.2.0",
"json5-loader": "^4.0.1",
"lite-server": "^2.6.1",
"webpack": "^5.6.0",
"webpack-cli": "^4.2.0"
}
...
And my webpack.config.js file looks like this:
const path = require("path");
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "myCode.js",
path: path.resolve(__dirname, "FinalCode")
},
resolve: {
extensions: ['.wasm', '.mjs', '.js', '.json']
}
};
So what exactly needs to be added or tweaked in all this - and how am I finally importing that .json file into my javascript app? Am I using an import statement? A require statement? Or something else?
Are you doing this import inside a function/if or something else? The error seems to suggest that.
Imports need to be static (unless you use dynamic imports that are a promise function) a difference with require that can be used anywhere.
Try putting that line on the top of the file and also rewrite it like this
import contractArtifact from "../build/contracts/MySmartContract.json"
You can change your MySmartContract.json to MySmartContract.js and the code inside will be:
export const MySmartContract = [{Your json data}]
end them import:
import { MySmartContract } from "../build/contracts/MySmartContract.js"
or try
import MySmartContract from "../build/contracts/MySmartContract.js"
then MySmartContract will be a JSONObject or JSONArray it depend on your json data

How to load polymer components correctly

I have a question with loading polymer element. When i configure my bower.json file dependency not loading correctly.
Here is my bower.json file:
{
"name": "Example",
"description": "Example",
"version": "1.0.0",
"license": "https://vaadin.com/license/cvtl-1",
"authors": [
"Vaadin Ltd"
],
"dependencies": {
"iron-flex-layout": "PolymerElements/iron-flex-layout#^2.0.0",
"iron-form": "PolymerElements/iron-form#^2.0.0",
"iron-media-query": "PolymerElements/iron-media-query#^2.0.0",
"polymer": "Polymer/polymer#^2.0.0",
"webcomponentsjs": "webcomponents/webcomponentsjs#^1.0.0",
"iron-icon": "^2.0.0",
"paper-toast": "^2.0.0",
"vaadin": "vaadin/vaadin#10.0.0-alpha8",
"vaadin-grid": "vaadin/vaadin-grid#4.1.0-beta1",
"vaadin-charts": "vaadin/vaadin-charts#6.0.0-alpha10",
"vaadin-valo-theme": "vaadin/vaadin-valo-theme#2.0.0-alpha5",
"vaadin-tabs": "^1.0.0",
"app-layout": "polymerelements/app-layout#2.1.0"
// "wysiwyg-e" : "^2.1.3"
},
"devDependencies": {
"web-component-tester": "Polymer/web-component-tester#^6.0.0"
},
"private": true,
"resolutions": {
"vaadin-grid": "4.1.0-beta1",
"vaadin-charts": "6.0.0-alpha10",
"vaadin-valo-theme": "2.0.0-alpha5"
}
}
I need to load correctly app-layout and wysiwyg-e. What did i do wrong ? Any suggestion ?
By looking at the other dependencies you're loading from the same directory, I would say that the path is case sensitive, and that for app-layout you only used lower case letters, instead of the PascalCase format, so maybe you should try:
"app-layout": "PolymerElements/app-layout#2.0.1",
(I am guessing you meant 2.0.1 there, not 2.1.0, since 2.0.5 seems to be the most recent version)
And for wysiwyg-e you can try:
"wysiwyg-e": "miztroh/wysiwyg-e#^2.1.3"
The reason for version conflicts with such bower.json file is that the individual Vaadin components (vaadin-grid, vaadin-charts, etc) are effectively included twice: once through the vaadin/vaadin meta-package and the second time explicitly.
Generally, having a bower dependency only to the vaadin meta-package should be enough:
"dependencies": {
"iron-flex-layout": "PolymerElements/iron-flex-layout#^2.0.0",
"iron-form": "PolymerElements/iron-form#^2.0.0",
"iron-media-query": "PolymerElements/iron-media-query#^2.0.0",
"polymer": "Polymer/polymer#^2.0.0",
"webcomponentsjs": "webcomponents/webcomponentsjs#^1.0.0",
"iron-icon": "^2.0.0",
"paper-toast": "^2.0.0",
"vaadin": "vaadin/vaadin#10.0.0-alpha8",
"wysiwyg-e": "^2.1.3"
},
"devDependencies": {
"web-component-tester": "Polymer/web-component-tester#^6.0.0"
}
This will pull a matching set of versions of vaadin-grid, vaadin-charts, and all other Vaadin components.
Note also, that you seem to be using an outdated alpha8 version. Please consider upgrading to the latest beta: vaadin/vaadin#10.0.0-beta8.
In case if you really need to use a specific version of say vaadin-grid, the way to do it is either to not use the vaadin meta-dependency at all, or to specify that version of it which includes the desired version of vaadin-grid (e.g. vaadin-grid#4.1.0-alpha1 is a part of vaadin#10.0.0-alpha5, but there is not version of the vaadin meta-package that includes the 4.1.0-beta1 version of vaadin-grid).

tell typescript to compile json files

The typescript compiler works fine when I import a json file using
const tasks = require('./tasks.json')
However, when I run tsc, the output directory does not contain no tasks.json file, causing a runtime error.
Is there a way to tell the compiler that it should copy all json files, or should I manually copy/paste all my json files into the dist directory ?
my tsc compilerOptions currently reads
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"sourceMap": true,
"noImplicitAny": true,
"removeComments": false,
"outDir": "./dist/",
"sourceMap": true,
"pretty": true,
"noImplicitThis": true,
"strictNullChecks": true,
"sourceMap": true
},
Thanks !
Problem
For people wanting to copy all JSON files, it's really difficult in TypeScript. Even with "resolveJsonModule": true, tsc will only copy .json files which are directly referenced by an import.
Here is some example code that wants to do a dynamic runtime require(). This can only work if all the JSON files have been copied into the dist/ folder, which tsc refuses to do.
// Works
import * as config from './config.default.json';
const env = process.env.NODE_ENV || 'development';
const envConfigFile = `./config.${env}.json`;
// Does not work, because the file was not copied over
if (fs.existsSync(envConfigFile)) {
const envConfig = require(envConfigFile);
Object.assign(config, envConfig);
}
Solution 1: Keep json files outside the src tree (recommended)
Assuming you have /src/ and /dist/ folders, you could keep your JSON files in the project's / folder. Then a script located at /src/config/load-config.ts could do this at runtime:
const envConfig = require(`../../config.${env}.json`);
// Or you could read manually without using require
const envConfigFile = path.join(__dirname, '..', '..', `config.${env}.json`);
const envConfig = JSON.parse(fs.readFileSync(envConfigFile, 'utf-8'));
This is the simplest solution. You just need to make sure the necessary config files will be in place in the production environment.
The remaining solutions will deal with the case when you really want to keep the config files in your src/ folder, and have them appear in your dist/ folder.
Solution 2: Manually import all possible files
For the above example we could do:
import * as config from './config.default.json';
import * as testingConfig from './config.testing.json';
import * as stagingConfig from './config.staging.json';
import * as productionConfig from './config.production.json';
This should cause the specified json files to be copied into the dist/ folder, so our require() should now work.
Disadvantage: If someone wants to add a new .json file, then they must also add a new import line.
Solution 3: Copy json files using tsc-hooks plugin (recommended)
The tsc-hooks plugin allows you to copy all files from the src tree to the dist tree, and optionally exclude some.
// Install it into your project
$ yarn add tsc-hooks --dev
// Configure your tsconfig.json
{
"compilerOptions": {
"outDir": "dist"
},
// This tells tsc to run the hook during/after building
"hooks": [ "copy-files" ]
// Process everything except .txt files
"include": [ "src/**/*" ],
"exclude": [ "src/**/*.txt" ],
// Alternatively, process only the specified filetypes
"include": [ "src/**/*.{ts,js,json}" ],
}
I found it tsc-hooks announced here.
Solution 4: Copy json files using an npm build script (recommended)
Before tsc-hooks, we could add a cpy-cli or copyfiles step to the npm build process to copy all .json files into the dist/ folder, after tsc has finished.
This assumes you do your builds with npm run build or something similar.
For example:
$ npm install --save-dev cpy-cli
// To copy just the json files, add this to package.json
"postbuild": "cpy --cwd=src --parents '**/*.json' ../dist/",
// Or to copy everything except TypeScript files
"postbuild": "cpy --cwd=src --parents '**/*' '!**/*.ts' ../dist/",
Now npm run build should run tsc, and afterwards run cpy.
Disadvantages: It requires an extra devDependency. And you must make this part of your build process.
Solution 5: Use js files instead of json files
Alternatively, don't use .json files. Move them into .js files instead, and enable "allowJs": true in your tsconfig.json. Then tsc will copy the files over for you.
Your new .js files will need to look like this: module.exports = { ... };
I found this idea recommended here.
Note: In order to enable "allowJs": true you might also need to add "esModuleInterop": true and "declaration": false, and maybe even "skipLibCheck": true. It depends on your existing setup.
And there is one other concern (sorry I didn't test this):
Will tsc transpile your config files if they are not all statically referenced by other files? Your files or their folders may need to be referenced explicitly in the files or include options of your tsconfig.json.
Solution 6: Use ts files instead of json files
Sounds easy, but there are still some concerns to consider:
Your config files will now look something like this: const config = { ... }; export default config;
See the note above about files / include options.
If you load the config files dynamically at runtime, don't forget they will have been transpiled into .js files. So don't go trying to require() .ts files because they won't be there!
If someone wants to change a config file, they should do a whole new tsc build. They could hack around with transpiled .js files in the dist folder, but this should be avoided because the changes may be overwritten by a future build.
Testing
When experimenting with this, please be sure to clear your dist/ folder and tsconfig.tsbuildinfo file between builds, in order to properly test the process.
(tsc does not always clean the dist/ folder, sometimes it just adds new files to it. So if you don't remove them, old files left over from earlier experiments may produce misleading results!)
In tsconfig.json, add
{
"compilerOptions": {
"resolveJsonModule": true,
},
"include": [
"src/config/*.json"
]
}
Notice that it won't copy those json files which are required. If you need to dynamically require some json files and need them to be copied to dist, then you need to change from, for example,
return require("some.json") as YourType
to
return (await import("some.json")) as YourType
.
In typescript 2.9+ you can use JSON files directly and it automatically copied to dist directories.
This is tsconfig.json with minimum needed configuration:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop" : true,
"module" : "commonjs",
"outDir" : "./dist",
"resolveJsonModule" : true,
"target" : "es6"
},
"exclude" : [
"node_modules"
]
}
Then you can create a json file.
{
"address": "127.0.0.1",
"port" : 8080
}
Sample usage:
import config from './config.json';
class Main {
public someMethod(): void {
console.log(config.port);
}
}
new Main().someMethod();
If you don't use esModuleInterop property you should access your json properties encapsulated in default field. config.default.port.
The typescript compiler works fine when I import a json file using
const tasks = require('./tasks.json')
TypeScript wouldn't complain about this as long as you have a global require() function defined, for example using node.d.ts. With a vanilla setup you would actually get a compile error that require is not defined.
Even if you've told TypeScript about a global require function it just sees it as a function that's expected to return something, it doesn't make the compiler actually analyze what the function is requiring ("tasks.json") and do anything with that file. This is the job of a tool like Browserify or Webpack, which can parse your code base for require statements and load just about anything (JS, CSS, JSON, images, etc) into runtime bundles for distribution.
Taking this a little further, with TypeScript 2.0 you can even tell the TypeScript Compiler about module path patterns that will be resolved and loaded by a bundler (Browserify or Webpack) using wildcard (*) module name declarations:
declare module "*.json" {
const value: any;
export default value;
}
Now you can import your JSON in TypeScript using ES6 module syntax:
import tasks from "./tasks.json";
Which will not give any compile error and will transpile down to something like var tasks = require("./tasks.json"), and your bundler will be responsible for parsing out the require statements and building your bundle including the JSON contents.
you can include this into your build script && ncp src/res build/res, will copy the files directly to your outDir
You can always get an absolute path to your project, with typescript code. To do it just read the JSON file not by the required keyword but with the help of the fs module. In a path of file use process.cwd() to access typescript project directory:
import * as fs from 'fs';
const task: any = JSON.parse(fs.readFileSync(`${process.cwd()}/tasks.json`).toString());
To make it work correctly you may need to change your running script to node dist/src/index.js where you specify a dist folder in the path.

"this" in underscore is undefined after compiling with browserify and debowerify

So first.. I have next gulp task:
gulp.task('js', function() {
browserify('./src/js/main.js')
.bundle()
.on('error', onError)
.pipe( source('main.js') )
.pipe( gulp.dest(path.build.js) );
});
and package.json:
{
"browserify": {
"transform": [
["babelify", { "presets": ["es2015"] }],
"debowerify"
]
},
}
I am importing Backbone in main.js (or only underscore... it doesn't matter)
import Backbone from 'backbone';
And in console I am getting error
Uncaught TypeError: Cannot read property '_' of undefined
I checked code and found that in underscore sources at start of library root is undefined
// Establish the root object, `window` in the browser, or `exports` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
I think the problem is that debowerify or babelfy is wrapping code in some function. But also if I use node modules without debowerify all works fine. But I want to use bower.
So how to fix this problem?
To any future visitors to this question,
this is similar to Underscore gives error when bundling with Webpack
The gist of the issue is that babel is probably running the underscore.js code, since underscore.js uses this, and in es6 this is undefined when outside of a function, naturally this._ fails.
In code I've fixed the issue by ensuring that babel does not run on node_modules.
In my case the same error arose when using just browserify with underscore. I've workarounded issue by switching from underscore to lodash. They are in general (surely not fully) compatible, but at the worst I'd rather copy some missing function from underscore sources than live with its deisolated load approach.

Adding a namespace to Laravel 4

I'm having a problem using a namespace in Laravel4.
We have built an API using Laravel3, in which we created an entire namespaced directory called Components which the RESTful Laravel controllers accessed to perform the logic on each request. The Components namespace was created in this manner so as to allow us to re-use the logic across several applications to keep things DRY.
In Laravel3, in the application/start.php file it was a simple matter of adding:
Autoloader::namespaces(array(
'Components' => 'path\to\Components',
));
This allowed us to simply reference a static method then in any of our RESTful controllers simply by
$result = Components\Services\Common::method();
In Laravel4, it is obviously a different approach. I have added the following to the composer.json file
"autoload": {
"classmap": [
"app/commands",
"app/controllers",
"app/models",
"app/database/migrations",
"app/database/seeds",
"app/tests/TestCase.php"
],
"psr-0": {
"Components": "path/to/API/Components"
}
},
and ran the composer dump-autoload command to add the namespace to the autoload_namespaces.php file.
However, I cannot reference the namespace in any of my new controllers in Laravel4. I just get a "Class 'Components\Services\Common' not found" in HomeController.php.
I have checked in the autoload_real.php file and output the loader variable, where my new namespace is listed under the 'C' element of the array. But no joy in using it.
I know the namespace works as it is in constant use with our Laravel3 applciation. I would rather not replicate the directory into our new Laravel4 application, otherwise the reason we designed things this way will be negated and we'll end up maintaining two codebases. The namespace directory exists within our web root directory but outside of both our Laravel3 and Laravel4 applications.
Thanks for the help guys
If your namespace is
Components
And your application is in
/var/www/application
And your namespaced classes are inside the subfolder
app/API
And this is an example of class file name:
/var/www/application/app/API/Components/Services/Common.php
Then you have to add to your composer json:
"autoload": {
"psr-0": {
"Components": "app/API"
}
},
If you are loading from another base path and your namespaced classes are in /var/www/Components, you can:
"autoload": {
"psr-0": {
"Components": "/var/www"
}
},
But if they are in /var/www/components/Components, then you have to
"autoload": {
"psr-0": {
"Components": "/var/www/components"
}
},
Because "Components" is the base of your namespaces and will always be added to the path before Composer search files to autoload.