I'm making an npm module using ES6 syntax (imports, exports etc) via webpack.
When I try to initialize an instance of the exported class in the frontend to test it, it isn't working and I've got a feeling it's to do with what CommonJS exports return vs ES6 exports.
example.js
export default class Example { ....
example-after-webpack-stuff.js
var Example = (function() ....
demo.js
var example = new Example();
demo.html
<script src="../example-after-webpack-stuff.js"></script>
<script src="demo.js"></script>
I receive the following:
Error: Uncaught TypeError: Example is not a function
EDIT
Webpack config:
module.exports = {
entry: './src/example.js',
output: {
filename: "./dist/example.js",
library: "Example",
libraryTarget: "var"
},
module: {
loaders: [
{
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['es2015']
}
}
]
}
};
Since babel 6, export default class Example will be compiled to exports.default = Example. While in Babel 5, it will be compiled to exports = Example. So you code will run without error using babel 5.
In babel 6, you can use CommonJS way module.exports:
class Example {
constructor() {
...
}
}
module.exports = Example;
Or you can use babel-plugin-add-module-exports to change babel 6's behavior.
npm install babel-plugin-add-module-exports --save-dev
Add it in webpack.config.js:
loaders: [
{
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['es2015'],
plugins: ['add-module-exports']
}
}
]
Related
I am creating a library in Javascript and I am shipping it as a bundle .js file using Webpack. The following file lib.js serves as the entry for Webpack in order to expose all the API in the library:
import * as bodies from "./bodies.js";
import * as composites from "./composites.js";
import * as connections from "./connections.js";
export var bodies = {
Body: bodies.Body,
Pyramid: composites.Pyramid
};
export var connections = {
Connection: connections.Connections
};
All the files imported basically export classes that I am referencing in lib.js:
// In bodies.js
export class Body { ... };
// In composites.js
export class Pyramid { ... };
// In connections.js
export class Connection { ... };
The file for bundling using Webpack is:
const path = require('path');
module.exports = {
entry: './lib.js',
output: {
filename: 'lib-bundle.js',
path: path.resolve(__dirname, 'out')
},
module: {
rules: [
/* In order to transpile ES6 */
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: { presets: ['env'] }
}
}
],
}
};
Webpack successfully bundle everything and I get my lib file in the end.
Problems using it
Then I use it in another project:
import * as mylib from "./lib/lib-bundle.js";
// Trying to use Pyramid
var pyramid = new mylib.bodies.Pyramid();
I use again Webpack to bundle this file into a file called start.js which i import in my page:
<script type="application/javascript" src="./start.js"></script>
However when running this page, I get an error. If I run the F12 tools and break in the bundle where I try creating an instance of the pyramid, there i can clearly see that object mylib does not have anything I have exposed. It is empty, lacking all the objects I exposed before.
What am I doing wrong?
You need to specify a libraryTarget in the output section of your webpack config file.
With it the bundle will correctly export your defined values, which
can be then imported with the various module loaders.
I suggest using libraryTarget: "umd" since it will add support for the most commonly used loaders. From the webpack docs:
This exposes your library under all the module definitions, allowing it to work with CommonJS, AMD and as global variable.
The resulting webpack config file is as follows:
const path = require('path');
module.exports = {
entry: './lib.js',
output: {
filename: 'lib-bundle.js',
path: path.resolve(__dirname, 'out'),
libraryTarget: 'umd',
},
module: {
rules: [
/* In order to transpile ES6 */
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: { presets: ['env'] }
}
}
],
}
};
I have multiple layouts, which depend on some shared typescript files, thats why I want to share this files with multiple layouts which are using webpack.
I'm trying to include jquery in my ajax.ts and get this error:
ERROR in ../_shared/ajax.ts
Module not found: Error: Can't resolve 'jquery' in '{...}/layouts/_shared'
_shared/ajax.ts:
import * as $ from 'jquery';
export class AjaxListener {
constructor(){
// use jquery with $
}
}
layoutA/app.ts:
import { AjaxListener } from "../_shared/ajax";
import { App } from "../_shared/app";
let app = new App();
let ajaxListener = new AjaxListener();
My Folder Structure looks like this:
/layouts
/_shared
/ajax.ts
/app.ts
/layoutA
/app.ts
/webpack.config.js
/package.json (contains "#types/jquery": "^2.0.47" and "jquery": "^3.2.1")
/tsconfig.json
tsconfig.json:
{
"compilerOptions": {
"module": "es6",
"target": "es6",
"sourceMap": true
},
"exclude": [
"node_modules",
"typings/browser",
"typings/browser.d.ts",
"typings/main",
"typings/main.d.ts"
]
}
webpack.config.js:
const ExtractTextPlugin = require("extract-text-webpack-plugin");
var path = require("path");
var distPath = path.join(__dirname, "dist");
module.exports = [
{
entry: {
app: ['./app.sass', './app.ts']
},
resolve: {
extensions: [".tsx", ".js", ".ts", ".sass"]
},
cache: false,
output: {
path: distPath,
filename: "[name]_scripts.js"
},
module: {
rules : [
{
enforce: 'pre',
// "test" is commonly used to match the file extension
test: /\.js$/,
loader: "source-map-loader"
},
{
// "test" is commonly used to match the file extension
test: /\.tsx?$/,
exclude: [/node_modules/],
use: [ 'babel-loader', 'ts-loader' ]
},
{
test: /\.sass$/,
use: [
{
loader: "style-loader" // creates style nodes from JS strings
},{
loader: "css-loader", options: { sourceMap: true } // translates CSS into CommonJS
},{
loader: "sass-loader", options: { sourceMap: true } // compiles Sass to CSS
}
]
}
]
},
devtool: "eval"
}
]
If I try to import jquery inside layoutA/app.ts file (webpack root), it works fine. Since the ajax.ts lives outside this folder, which is the best way to import libraries like jquery, lodash etc. in these files?
The following points must be observed for the best way to load js libraries in your context:
Install every js library (e.g. jquery) with a package manager like npm
To each library it needs a TypeScript definitions file (e.g. #types/jquery, to find under npmjs.com)
Install this TypeScript definition file also with npm
Note every TypeScript definition in the tsconfig.json under "files" like
"files":[
"node_modules/#types/jquery/index.d.ts",
"node_modules/#types/requirejs/index.d.ts",
]
Do this compellingly (point 1-4) with the library requirejs. This is a js file and module loader.
Now you are ready to load the js library in the TypeScript main file like:
require(["jquery", "urijs"], function($, uri) {
// your code
});
Other notes:
In the index.html file: reference under the script tags only the js bundle files, builded by e.g. webpack.
Webpack needs a TypeScript loader.
Reference exported TypeScript classes in the tsconfig.json file under 'files' and also in the TypeScript file like:
import {Car} from "./classes/Car";
Hope it helps you, to get a proper structur!
Supplement:
Try the following: reference a js library in your ayax.ts like:
private example = require("./[path to npm library]/node_modules/test/src/test.js");
If you call the library name like 'test' in the require command, then its not possible to resolve 'test'. It try to resolve over the package.json and can not find it because its outside of the root.
I have simple app wrote in ES6 for the training purpose. I want to use modules in this app, so I installed webpack and babel. Unfortunetaly, when I try to fire my method, I receive the error:
Uncaught ReferenceError: gas is not defined
at HTMLButtonElement.count (bundle.js:113)
So, 'gas' is my instance of the object.
Here are my files:
main.js
'use strict'
import Gas from "./gas";
const count = document.getElementById("gas-count");
const gas = new Gas();
count.addEventListener("click", gas.count);
gas.js
export default class Gas {
constructor() {
}
count() {
// code
}
printResult(result) {
// code
}
_isValid(dist, price, aver) {
// Code
}
};
Finally, here is my Webpack config:
module.exports = {
// Define entry point
entry: "./src/main.js",
// Define output point
output: {
path: __dirname + "/dist",
filename: "bundle.js"
},
module: {
loaders: [{
test: /\.js$/,
exclude: /(node_modules)/,
loader: "babel-loader",
query: {
presets: ["env"]
}
}]
}
};
I'd be really grateful if someone could give me a hint why it's not working. Thank you in advance.
I want to use the css-loader with the 'modules' option of webpack in a React application written in Typescript. This example was my starting point (they are using Babel, webpack and React).
webpack config
var webpack=require('webpack');
var path=require('path');
var ExtractTextPlugin=require("extract-text-webpack-plugin");
module.exports={
entry: ['./src/main.tsx'],
output: {
path: path.resolve(__dirname, "target"),
publicPath: "/assets/",
filename: 'bundle.js'
},
debug: true,
devtool: 'eval-source-map',
plugins: [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({minimize: true})
],
resolve: {
extensions: ['', '.jsx', '.ts', '.js', '.tsx', '.css', '.less']
},
module: {
loaders: [
{
test: /\.ts$/,
loader: 'ts-loader'
},
{
test: /\.tsx$/,
loader: 'react-hot!ts-loader'
}, {
test: /\.jsx$/,
exclude: /(node_modules|bower_components)/,
loader: "react-hot!babel-loader"
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
loader: "babel-loader"
}, {
test: /\.css/,
exclude: /(node_modules|bower_components)/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader')
}
]
},
plugins: [
new ExtractTextPlugin("styles.css", {allChunks: true})
],
postcss: function() {
return [require("postcss-cssnext")()]
}
}
This is a React component I want to style with an accompanying CSS file:
import React = require('react');
import styles = require('../../../css/tree.css')
class Tree extends React.Component<{}, TreeState> {
...
render() {
var components = this.state.components
return (
<div>
<h3 className={styles.h3} >Components</h3>
<div id="tree" className="list-group">
...
</div>
</div>
)
}
}
export = Tree
tree.css
.h3{
color: red;
}
No matter what I'm doing (tried changing the import syntax, tried declaring the 'require' for ts-loader, described here, I always get:
Uncaught Error: Cannot find module "../../../css/tree.css"
at runtime and
error TS2307: Cannot find module '../../../css/tree.css'.
by the TS compiler. Whats happening? Seems to me that css-loader is not even emitting ICSS? Or is it ts-loader behaving wrong?
import has special meaning to TypeScript. It means that TypeScript will attempt to load and understand the thing being imported. The right way is to define require like you mentioned but then var instead of import:
var styles = require('../../../css/tree.css')`
Declare 'require' as per ts-loader documentation.
Use 'require' as generic with < any > type: require< any >("../../../css/tree.css").
*.d.ts file
declare var require: {
<T>(path: string): T;
(paths: string[], callback: (...modules: any[]) => void): void;
ensure: (paths: string[], callback: (require: <T>(path: string) => T) => void) => void;
};
*.tsx file with component
const styles = require<any>("../../../css/tree.css");
...
<h3 className={styles.h3}>Components</h3>
I know it was already answered, but I was struggling with it for a while before I realized I need to use generic type specification, without that I wasn't able to access content of CSS file. (I was getting error: Property 'h3' does not exists on type '{}'.)
I had similar problem.
For me, works import:
import '../../../css/tree.css';
Webpack change this like any other normal imports. It change it to
__webpack_require__(id)
One drawback is that you lost control on style variable.
You can use https://github.com/Quramy/typed-css-modules, which creates .d.ts files from CSS Modules .css files. Please see also https://github.com/css-modules/css-modules/issues/61#issuecomment-220684795
A bit late to game but you can create a file called tree.css.d.ts in the same folder as tree.css that has this line:
export const h3: string;
and still use the import statement import * as styles from ... and you will still getcode completion and compile time checking.
You can either manage these definition files manually or you could integrate typed-css-modules into your build pipeline (https://github.com/Quramy/typed-css-modules)
I am trying to write my first babel program and kind of stuck.
I wrote script 1
var message = "Hello World";
module.exports = message;
and script2
var message = require('./script1');
document.write(`This is formatted with ES6 ${message}`);
my webpack.config.js looks like
module.exports = {
entry: {
main: [
'./script1.js',
'./script2.js'
]
},
output: {
filename: "./public/[name].js"
},
loaders: {
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel'
}
}
The above code works and I am able to see the output but now if I modify script2 to
import message from './script1';
document.write(`This is formatted with ES6 ${message}`);
then when I run webpack it says
ERROR in ./script2.js
Module parse failed: /Users/a.c/MyProjects/ReactTutorial/script2.js Line 1: Unexpected token
You may need an appropriate loader to handle this file type.
| import message from './script1';
| document.write(`This is formatted with ES6 ${message}`);
# multi main
My understanding is that because I am using babel, I should be able to use the new ES6 way of importing stuff into my code easily.
Try add resolve.extensions to config file (in order to avoid always write extensions when you import .js or .jsx files) also if you are using babel 6 you need install couple packages babel-preset-es2015 and babel-preset-react
module.exports = {
entry: {
main: [
'./script1.js',
'./script2.js'
]
},
output: {
filename: "./public/[name].js"
},
loaders: {
test: /\.jsx?$/, // or /\.(js|jsx)$/
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
}
},
resolve: {
extensions: ['', '.js', '.jsx']
}
}
Most likely you have forgotten to specify es2015 preset for babel.
Make sure it's installed:
> npm i -D babel-preset-es2015
You have two options to specify this preset for babel.
Create .babelrc file and specify the preset there:
{
"presets": ["es2015"]
}
Specify the preset using query property:
module: {
loaders: [
{
test: /\.jsx?$/,
include: /src/,
loader: 'babel',
query: {
presets: ['es2015']
}
}
]
}