I have a Javascript file utils.js which contains some utility functions. Here is an example:
export function RemoveHTML(Str) {
return Str.replace(/<[^>]*(?:>|$)/gi,'');
}
I can use these functions by importing the utils.js file like such:
import {RemoveHTML} from '../js/utils.js';
I also have a model.js file for some data queries like this (pseudo-code for brevity):
async function getStuff() {
await DBConnection.connect();
return results
}
module.exports = {
getStuff
}
For 'consistency' I thought I'd change model.js to just this:
export async function getStuff() {
await DBConnection.connect();
return results
}
If I do that then I get an app crash error stating:
SyntaxError: Unexpected token 'export'
What is the difference between exporting a function using export function() and module.exports?
UPDATE:
I am using Babel in Webpack like below so why would I get an error?:
{
test: /\.js$/,
include: [ srcPath ],
exclude: ['/node_modules/'],
use: {
loader: 'babel-loader',
options: {
presets: ["#babel/preset-env"] //Preset used for env setup
}
}
},
export function() is an ES6 syntax used for exporting while module.exports is or exports is a special object which is included in every JS file in the Node.js application by default. You can also use the ES6 syntax for exporting/importing a module in Node.js but for using that you will have to transpile the new ES6 code to Node.js supported ES5 format, You can easily do that using babel (which is a tool for transpiling JavaScript).
#JonasWilms is definitely correct about the point he made. I see you are using commonjs on the server code and es6 on the client-side.
There is no difference between module.export or export. In your project, your server code is using commonjs modules, so you should use module.exports. In your client code, keep on using export (es6) syntax of JavaScript.
But if you want to write your javascript globally with es6, you will have to install some dependencies and configure your babel.
Check out this link https://www.codementor.io/#iykyvic/writing-your-nodejs-apps-using-es6-6dh0edw2o
Related
I'm working on an extension system for my web app. Third-party developers should be able to extend the app by providing named AMD modules exporting constants and functions following a predefined spec and bundled into a single .js JavaScript file.
Example JavaScript bundle:
define('module1', ['exports', 'module3'], (function (exports, module3) {
exports.spec = 'http://example.com/spec/extension/v1'
exports.onRequest = function (request) { return module3.respond('Hello, World.') }
}));
define('module2', ['exports', 'module3'], (function (exports, module3) {
exports.spec = 'http://example.com/spec/extension/v1'
exports.onRequest = function (request) { return module3.respond('Foo. Bar.') }
}));
define('module3', ['exports'], (function (exports) {
exports.respond = function (message) { return { type: 'message', message: message } }
}));
In the above example module1 and module2 are extension modules (identified by the spec export) and module3 is a shared dependency (e.g. coming from an NPM package). Extension bundles will be loaded in a worker within a sandboxed iframe to seal of the untrusted code in the browser.
Example TypeScript source:
// module1.ts
import respond from 'module3'
export const spec = 'http://example.com/spec/extension/v1'
export const onRequest = (request: Request): Response => respond('Hello, World.')
// module2.ts
import respond from 'module3'
export const spec = 'http://example.com/spec/extension/v1'
export const onRequest = (request: Request): Response => respond('Foo. Bar.')
// module3.ts
import dep from 'some-npm-package'
export respond = (message: string) => dep.createMessageObject(message)
Here is my list of requirements to bundling:
All necessary dependencies (e.g. shared module, NPM package logic) must be included in the bundle
The source code needs to be transpiled to browser compatible code if necessary
The AMD format is required by the custom extension loader implementation
The AMD modules must not be anonymous as the module file names are lost while bundling
No relative paths must be used among dependencies (e.g. ./path/to/module3 instead of module3)
The result should be one JavaScript bundle, thus ONE JavaScript file and ONE sourcemaps file
What's the easiest way to do this?
This is the closest solution I found using rollup and the following rollup.config.js:
import { nodeResolve } from '#rollup/plugin-node-resolve'
import { terser } from 'rollup-plugin-terser'
import typescript from '#rollup/plugin-typescript'
export default {
input: [
'src/module1.ts',
'src/module2.ts'
],
output: {
dir: 'dist',
format: 'amd',
sourcemap: true,
amd: {
autoId: true
}
},
plugins: [
typescript(),
nodeResolve(),
terser()
]
}
From this I get the desired named AMD modules (one for each entry point and chunk) in separate .js files. Problems:
Some dependencies are referenced by ./module3 while being named module3.
The modules appear in separate JavaScript and Sourcemap files instead of being concatenated into a single bundle.
Questions:
Is there an easy fix to the above rollup.config.js config to solve the problem?
I tried to write a small rollup plugin but I failed to get the final AMD module code within it to concatenate it to a bundle. Only the transpiled code is available to me. In addition I don't know how to handle sourcemaps during concatenation.
Is there an alternative to rollup better suited to this bundling scenario?
The big picture: Am I completely on the wrong track when it comes to building an extension system? Is AMD the wrong choice?
I found a way to extend the rollup.config.js mentioned in the question with a custom concatChunks rollup plugin to bundle multiple AMD chunks within a single file and having the source maps rendered, too. The only issue I didn't find an answer to was the relative module names that kept popping up. However, this may be resolved in the AMD loader.
Here's the full rollup.config.js that worked for me:
import Concat from 'concat-with-sourcemaps'
import glob from 'glob'
import typescript from '#rollup/plugin-typescript'
import { nodeResolve } from '#rollup/plugin-node-resolve'
import { terser } from 'rollup-plugin-terser'
const concatChunks = (
fileName = 'bundle.js',
sourceMapFileName = 'bundle.js.map'
) => {
return {
name: 'rollup-plugin-concat-chunks',
generateBundle: function (options, bundle, isWrite) {
const concat = new Concat(true, fileName, '\n')
// Go through each chunk in the bundle
let hasSourceMaps = false
Object.keys(bundle).forEach(fileId => {
const fileInfo = bundle[fileId]
if (fileInfo.type === 'chunk') {
let hasSourceMap = fileInfo.map !== null
hasSourceMaps = hasSourceMaps || hasSourceMap
// Concat file content and source maps with bundle
concat.add(
fileInfo.fileName,
fileInfo.code,
hasSourceMap ? JSON.stringify(fileInfo.map) : null
)
// Prevent single chunks from being emitted
delete bundle[fileId]
}
})
// Emit concatenated chunks
this.emitFile({
type: 'asset',
name: fileName,
fileName: fileName,
source: concat.content
})
// Emit concatenated source maps, if any
if (hasSourceMaps) {
this.emitFile({
type: 'asset',
name: sourceMapFileName,
fileName: sourceMapFileName,
source: concat.sourceMap
})
}
}
}
}
export default {
input: glob.sync('./src/*.{ts,js}'),
output: {
dir: 'dist',
format: 'amd',
sourcemap: true,
amd: {
autoId: true
}
},
plugins: [
typescript(),
nodeResolve(),
terser(),
concatChunks()
]
}
Please make sure you npm install the dependencies referenced in the import statements to make this work.
Considering the big picture, i.e. the extension system itself, I am moving away from a "one AMD module equals one extension/contribution" approach, as current developer tools and JavaScript bundlers are not ready for that (as this question shows). I'll go with an approach similar to the Visual Studio Code Extension API and will use a single "default" module with an activate export to register contributions a bundle has to offer. I hope that this will make extension bundling an easy task no matter what tools or languages are being used.
What I'm trying to achieve is to use dynamically created object in webpack config in a typescript script project (that uses esnext module syntax like import, export, dynamic import())
i.e. something like below (in DefinePlugin)
File - webpack.config.js
const productA = require("./<path>/ProductA");
const productB = require("./<path>/ProductB");
let product;
switch(process.env.PRODUCT_TYPE) {
case 'ProductA':
product = new ProductA();
break;
case 'ProductB':
product = new ProductB();
break;
default:
console.log("ERROR - Incorrect product type")
process.exit(-1);
}
...
...
{
entry: './src/entry.ts',
module: {
rules: [{
test: /\.tsx?$/,
use: 'ts-loader'
}]
},
...
...
plugins: [
new webpack.DefinePlugin({
productType: () => {
return product;
}
})
]
}
Script - DynamicLoad.ts
let product = productType();
product.run();
But, the problem I'm having is with esnext syntax types i.e. ProductA and ProductB classes here uses esnext syntax like import, export and implements keywords. So, webpack throws error when imported these classes into the webpack.config.js files.
So, is there a way to make webpack.config.js understand the esnext syntax?
Different workarounds tried and failed
Use babel
babel supports imports, export and dynamic import() syntax. So, followed How can I use ES6 in webpack.config.js? and tried with babel (i.e. webpack.config.babel.js)
Issue: But, with this the errors I faced:
babel not able to identify implements keyword.
couldn't find .css module (my webpack has css-loader and style-loader)
webpack in typescript format
Webpack support typescript formatted config i.e. webpack.config.ts
Ref - https://webpack.js.org/configuration/configuration-languages/#root
Issue: But, the issue with this is webpack.config.ts needs module type of commonJS whereas my project uses esnext. So, many syntax identification errors are generated.
So, is there any way for me to achieve this in webpack?
I am setting up a boilerplate running Gulp with Babel. I have created 2 simple files (app.js and Caller.js) to test the environment.
Caller.js:
export default class Caller {
constructor() {
//
}
SayHello() {
alert("Hello!!");
}
}
App.js:
import Caller from './Caller.js';
let x = new Caller();
x.SayHello();
App.js is referenced in index.html along with:
<script src="/js/Caller.js"></script>
<script src="/js/app.js"></script>
My Gulp task looks like this:
gulp.task('js', () => {
pump([
gulp.src(src.js),
plumber(err => console.error(err)),
babel({ presets: ['es2015', 'es2017'] }),
gulp.dest(dest.js),
browserSync.stream()
]);
});
Note: I am trying to be as supportive of older browsers as possible, so I want to include ES2015.
What I find is that when I include es2015, rather than es2017 alone, I get the following errors:
ReferenceError: exports is not defined
ReferenceError: require is not defined
My understanding is that it is possible to transpile this code into ES2015 compatible code, yet if I remove ES2015 from the Babel presets in Gulp it works fine.
Why?
I have somewhere in my code following construction:
var getMenu = function () {
return window.fetch("portal/content/json/menu.json").then(function (data) {
return data.json();
});
};
I tried in my webpack.config.js this:
module: {
loaders: [
...
{
test: /\.json$/,
exclude: /node_modules/,
use: [
'file-loader?name=[name].[ext]&outputPath=portal/content/json'
]
},
...
]
}
Project structure:
dist
content
json
menu.json <- this is missing
src
content
json
menu.json <- source file
Question:
How can webpack copy src/content/json/menu.json to dist/content/json/menu.json ?
You're using fetch to request a JSON file and that will only happen at runtime. Furthermore, webpack only processes anything that is imported. You expected it to handle an argument to a function, but if webpack did that, every argument to a function would be considered a module and that breaks any other use for that function.
If you want your loaders to kick in, you can import the file.
import './portal/content/json/menu.json';
You can also import the JSON and use it directly instead of fetching it a runtime. Webpack 2 uses json-loader by default for all .json files. You should remove the .json rule and you would import the JSON as follows.
import menu from './portal/content/json/menu.json';
menu is the same JavaScript object that you would get from your getMenu function.
if you'd like your json to be loaded in runtime/deferred you can use awesome webpack's dynamic imports feature:
import(
/* webpackChunkName: "json_menu" */
'./portal/content/json/menu.json'
);
it will return a Promise which resolves to the module object, with "default" field containing your data. So you might want something like this (with es6 it looks really nice):
import(
/* webpackChunkName: "json_menu" */
'./portal/content/json/menu.json'
).then(({default: jsonMenu}) => {
// do whatever you like with your "jsonMenu" variable
console.log('my menu: ', jsonMenu);
});
Notice that dynamic imports require a babel plugin syntax-dynamic-import, install it with npm:
npm i babel-plugin-syntax-dynamic-import -D
Have a nice day
I'm writing some frontend code with ECMAScript 6 (transpiled with BabelJS, and then browserified with Browserify) so that I can have a class in one file, export it and import it in another file.
The way I'm doing this is:
export class Game {
constructor(settings) {
...
}
}
And then on the file that imports the class I do:
import {Game} from "../../lib/pentagine_browserified.js";
var myGame = new Game(settings);
I then compile it with grunt, this is my Gruntfile:
module.exports = function(grunt) {
"use strict";
grunt.loadNpmTasks('grunt-babel');
grunt.loadNpmTasks('grunt-browserify');
grunt.initConfig({
"babel": {
options: {
sourceMap: false
},
dist: {
files: {
"lib/pentagine_babel.js": "lib/pentagine.js",
"demos/helicopter_game/PlayState_babel.js": "demos/helicopter_game/PlayState.js"
}
}
},
"browserify": {
dist: {
files: {
"lib/pentagine_browserified.js": "lib/pentagine_babel.js",
"demos/helicopter_game/PlayState_browserified.js": "demos/helicopter_game/PlayState_babel.js"
}
}
}
});
grunt.registerTask("default", ["babel", "browserify"]);
};
However, on the new Game( call, I get the following error:
Uncaught TypeError: undefined is not a function
As so, what I did was analyse the generated code by Babel and Browserify and I found this line on PlayState_browserified.js:
var Game = require("../../lib/pentagine_browserified.js").Game;
I decided to print the require output:
console.log(require("../../lib/pentagine_browserified.js"));
And it is nothing but an empty object. I decided to check out the pentagine_browserified.js file:
var Game = exports.Game = (function () {
It seems like it is correctly exporting the class, but for some other reason it is not being required on the other file.
Also, I'm sure the file is being required properly because changing the string "../../lib/pentagine_browserified.js" spits out a Not Found error, so it is going for the right file, that I'm sure about.
Browserify is meant to be fed a single "entry point" file, through which it recursively traverses all of your require statements, importing the code from other modules. So you should be require'ing the _babel.js versions of modules, not _browserified.js ones.
From the looks of it, you intend for your app's "entry point" to be demos/helicopter_game/PlayState_browserified.js, yeah? If that's the case:
In PlayState.js, change it to import {Game} from "../../lib/pentagine_babel.js";.
In Gruntfile.js, remove "lib/pentagine_browserified.js": "lib/pentagine_babel.js".
Works for me. Let me know if that suffices or I am misunderstanding your requirements here.
P.S. You can use babelify to avoid having separate Grunt tasks for Babel and Browserify. See my answer here for an example.
I had a slightly different file configuration, that gave me some difficulty to get the "require" syntax to work in Node, but this post gave me the hint on how to used the babel-ified version of the file name.
I am using WebStorm with the FileWatcher option set to Babel, and I have the FileWatcher configured to watch all files with suffix .jsx, and rename the compiled output file from {my_file}.jsx to {my_file}-compiled.js.
So in my test case, I have 2 files:
Person.jsx:
class Person { ... }
export { Person as default}
and another file that wants to import it:
Test.jsx:
var Person = require('./Person-compiled.js');
I couldn't get the "require" statement to find the module until I started the file path with './' and also add '-compiled.js' to properly specify the file name so that Node es5 could find the module.
I was also able to use the "import" syntax:
import Person from './Person-compiled.js';
Since I have set up my WebStorm project as a Node ES5 project, I have to run 'Test-compiled.js' (not 'Test.jsx').