RequireJs bundling with modules - javascript

I recently saw an example of how requirejs bundles work(http://vimeo.com/97519516) and got excited. So I'm trying to get the bundling feature to work with my setup and am banging my head on the wall.
Troubleshooting done so far.
The main.js file will be created properly without bundling and defining the comp1 component in the main.js top depenendecy. But when I nest the comp1 dependency and add the bundling options the page1 and page2 bundle never gets created. So it doesn't seem to be an issue with the rest of the configuration. I have also tried this a few different ways. Removing the module and and putting everything in the root of the config. Moving the bundles to the all-config.js. Any ideas on what I could be doing wrong.
all-config.js
(function() {
if (typeof requirejs != 'function') {
requirejs = function(config) { requirejs = config; };
}
requirejs({
pathes: {
comp1: "path1",
comp2: "path2,
router: "path3"
}
});
build.json
{
"baseUrl": "./",
"appDir": "../src/main/webapp",
"dir": "build",
"mainConfigFile": "app/all-config.js",
modules: [
{
"include": [
"app/lib/require/require.js"
],
bundles":{
"page1" : ["comp1"],
"page2" : ["comp2"]
}
}
]
}
main.js
require(['jquery', 'router'],function($){
//load app
require(["comp1"],function(comp1){
var app = new comp1();
});
});
Gruntfile
module.exports = function(grunt){
var stripper = require('strip-json-comments');
var buildOptionsFile = grunt.file.read( 'build.json' );
var buildOptions = JSON.parse(stripper(buildOptionsFile) );
grunt.initConfig({
requirejs: {
compile: {
options: buildOptions
}
}
});
.....
};
Any ideas on how to get this to work?

Related

How to Access jQuery Plugin From A Separate Webpack Bundle

I'm bundling JS and CSS (compiled from SCSS) into two separate bundles, one for 3rd party (vendor) and one for the project code (company). I'm able to access jQuery via $ successfully from scripts in the company bundle as a global, such as from some-other-script.js, without any issues. However when trying to call the stickyTableHeaders function from the StickyTableHeaders plugin in table-headers.js: Uncaught TypeError: $(...).stickyTableHeaders is not a function. I don't get any other errors about loading scripts etc. and I can see that vendor.bundle.js includes the plugin code.
Additionally I see from the bottom of the plugin source that the function is meant to be added to $ as follows:
$.fn[name] = function ( options ) {
return this.each(function () {
var instance = $.data(this, 'plugin_' + name);
if (instance) {
if (typeof options === 'string') {
instance[options].apply(instance);
} else {
instance.updateOptions(options);
}
} else if(options !== 'destroy') {
$.data(this, 'plugin_' + name, new Plugin( this, options ));
}
});
};
Any ideas why it can't find the function on the $ (jQuery) object?
This question seems similar, however the poster was having trouble with the plugin not being able to find jQuery in that case. Additionally I'm not sure if using the import-loader as per one of the suggestions is the right approach in my case, or if I'm doing something fundamentally wrong. You can see commented out lines in the webpack.config.js below where I've tried to register sticky-table-headers as a plugin with webpack without success - same result.
My webpack.config.js is as follows:
var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var extractSass = new ExtractTextPlugin({
filename: "[name].bundle.css",
disable: process.env.NODE_ENV === "development"
});
module.exports = function (env) {
env = env || {};
var isProd = env.NODE_ENV === 'production';
// Setup base config for all environments
var config = {
entry: {
vendor: './Client/js/vendor',
company: './Client/js/company' // Includes all SCSS, which ends up in company.bundle.css via extract-text-webpack-plugin.
},
output: {
// ReSharper disable once UseOfImplicitGlobalInFunctionScope
path: path.join(__dirname, 'wwwroot/dist'),
filename: '[name].bundle.js'
},
devtool: 'eval-source-map',
mode: "development",
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
alias: {
"jquery.validation": "jquery-validation/dist/jquery.validate.js",
//"sticky-table-headers": "sticky-table-headers/js/jquery.stickytableheaders.js"
}
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
Popper: ['popper.js', 'default'],
//"sticky-table-headers": ["sticky-table-headers", "default"]
}),
extractSass
],
module: {
rules: [
{
test: /\.scss$/,
use: extractSass.extract({
use: [{
loader: "css-loader"
}, {
loader: "sass-loader"
}],
// use style-loader in development
fallback: "style-loader"
})
}
]
}
}
// Alter config for prod environment
if (isProd) {
config.devtool = 'source-map'; // SourceMap emitted as a separate file.
//Normally disallow access on webserver or use (none) instead. Internal
//app so leaving them accessible for easier support.
config.mode = "production";
}
return config;
};
Then in vendor.js I have:
import 'jquery';
import 'popper.js';
import 'bootstrap';
import "jquery-validation";
import "jquery-validation-unobtrusive";
import "sticky-table-headers";
In company.js I have:
import '../scss/site.scss';
import './site';
import './some-other-script';
import './table-headers';
Finally in table-headers.js I have:
(function () {
$(function () {
if ($(".my-sticky-table-header").length === 0) return;
var offset = $('.navbar').height();
$(".my-sticky-table-header").stickyTableHeaders({
fixedOffset:offset});
});
})();
Thanks.
Looks like there was a fundamental flaw with this setup. I ended up adding:
import './vendor';
to the top of company.js and then using the SplitChunks plugin mentioned here to avoid everything in the vendor bundle being duplicated. This allowed library functions to be called from the company bundle.
(Something like this may have worked but it seems messy).

How do I reference or access my classes from a bundle created by webpack?

I am following this tutorial to learn webpack. It's out-dated, since the tools referenced are upgraded in my case. My versions are as follows.
ts-loader ^1.3.3
tsd ^0.6.5
typescript ^2.1.5
webpack ^1.14.0
After successfully creating a bundle.js file, I seem to be having problems accessing/referencing my TypeScript to JavaScript classes. My tsconfig.json looks like the following.
{
"compilerOptions": {
"module": "commonjs",
"sourceMap": true,
"declaration": true
},
"include": [
"src/**/*"
]
}
My wepack.config.js looks like the following.
var webpack = require('webpack');
module.exports = {
entry: './src/app.ts',
output: {
filename: 'bundle.js'
},
devtool: 'source-map',
resolve: {
extensions: ['', '.webpack.js', '.web.js', '.ts', '.js']
},
module: {
loaders: [
{ test: /\.ts$/, loader: 'ts-loader' }
]
}
}
To transpile the TypeScript code, I simply type in tsc. To bundle all the code into bundle.js, I simply type in webpack. (tsc and webpack are installed globally).
In my src directory, I only have 2 simple classes. The car.ts looks like the following.
export class Car {
public sayHi(): void {
console.log('car says hi');
}
}
The app.ts looks like the following.
import { Car } from './car'; //alternatively, import Car = require('./car');
let car = new Car();
car.sayHi();
I then load this bundle.js into a browser in a HTML page as follows.
<html>
<head>
<title>Test Webpack</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
I can see in the (Chrome) JavaScript console the log message "car says hi". But how do I create a new instance of Car? The following attempts at the JavaScript console doesn't work.
var c1 = new Car(); //Uncaught reference error
var c2 = new car.Car(); //Uncaught reference error
Inside bundle.js, I do see this code/modules being generated (snippet).
/* 0 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
var car_1 = __webpack_require__(1);
var car = new car_1.Car();
car.sayHi();
/***/ },
/* 1 */
/***/ function(module, exports) {
"use strict";
var Car = (function () {
function Car() {
}
Car.prototype.sayHi = function () {
console.log('car says hi');
};
return Car;
}());
exports.Car = Car;
/***/ }
My end goal is to get all TypeScript files in the src directory transpiled with (TypeScript) mappings into a single bundle.js as a module so I can use/reference the code (as JavaScript in a browser) as follows.
var car = new mylib.Car();
var plane = new mylib.Plane();
car.sayHi();
plane.sayHi();
Any ideas on what I'm doing wrong?
You need to set the output.library setting:
output: {
...
library: 'MyLibrary'
}
Then you can use it this way in the console:
var x = new MyLibrary.Car();
Webpack documentation (output.libray)
Similar answer on SO
What you should do is let webPack handle the transpilation for you as well.
Using a TypeScript loader.
What webpack does is making modules (this last file you put, everything is inside a function with context and without the Name you gave on a global space).Any global names are bound to the scope (or changed if you ugligy) so You can't access them from the console. Make webpack use the typescript loader and you can reference the car simply requiring or importing the file.

Exported TypeScript Class not included in WebPack bundle if not used directly

I'm converting a javascript project with Angular 1.x to WebPack and TypeScript (using ts-loader). I got it mostly working, but I'm running into trouble when ts-loader seems to be optimizing my scripts out of the bundle when the exports are not directly used.
Here's a sample project demonstrating the issue (npm install, webpack, then load index.html and watch the console).
https://github.com/bbottema/webpack-typescript
The logging from ClassA is showing up, but angular is reporting ClassB missing (provider). If you look in bundle.js you'll notice ClassB missing entirely. The difference is ClassA begin use directly after importing, and ClassB is only referenced by type for compilation.
Is it a bug, or is there a way to force ClassB to be included? Or am I going about it wrong? Angular 2 would probably solve this issue, but that's a step too large right now.
Relevant scripts from the project above:
package.json
{
"devDependencies": {
"typescript": "^1.7.5",
"ts-loader": "^0.8.1"
},
"dependencies": {
"angular": "1.4.9"
}
}
webpack.config.js
var path = require('path');
module.exports = {
entry: {
app: './src/entry.ts'
},
output: {
filename: './dist/bundle.js'
},
resolve: {
root: [
path.resolve('./src/my_modules'),
path.resolve('node_modules')
],
extensions: ['', '.ts', '.js']
},
module: {
loaders: [{
test: /\.tsx?$/,
loader: 'ts-loader'
}]
}
};
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs"
},
"exclude": [
"node_modules"
]
}
index.html
<!doctype html>
<html ng-app="myApp">
<body>
<script src="dist/bundle.js"></script>
</body>
</html>
entry.js
declare var require: any;
'use strict';
import ClassA = require('ClassA');
import ClassB = require('ClassB');
var a:ClassA = new ClassA(); // direct use, this works
var angular = require('angular');
angular.module('myApp', []).
// this compiles as it should, but in runtime the provider will not be packaged and angular will throw an error
run(function(myProvider: ClassB) {
}
);
ClassA.ts
// this line will be logged just fine
console.log('ClassA.ts: if you see this, then ClassA.ts was packaged properly');
class ClassA {
}
export = ClassA;
ClassB.ts
declare var require: any;
// this line is never logged
console.log('ClassB.ts: if you see this, then ClassB.ts was packaged properly');
class ClassB {
}
var angular = require(angular);
angular.module('myApp').service(new ClassB());
export = ClassB;
Turns out you have to signal WebPack to explicitly include a module by adding an extra require call without import statement.
I'm not ready to mangle my .ts files by adding duplicate imports, so I made a generic solution for that using the preprocessor loader:
{
"line": false,
"file": true,
"callbacks": {
"fileName": "all",
"scope": "line",
"callback": "(function fixTs(line, fileName, lineNumber) { return line.replace(/^(import.*(require\\(.*?\\)))/g, '$2;$1'); })"
}]
}
As a proof of concept, this regex version is very limited it only support the following format:
import ClassA = require('ClassA');
// becomes
require('ClassA');import ClassA = require('ClassA');
But it works for me. Similarly, I'm adding the require shim:
{
"fileName": "all",
"scope": "source",
"callback": "(function fixTs(source, fileName) { return 'declare var require: any;' + source; })"
}
I made a sample project with this solution.

Systemjs-Builder - Cannot configure properly - Bundling Typescript into a package

I want to build a quick nodejs script to package a Typescript app as SystemJS modules, a lot like what Angular2 bundles look like.
I tried different configurations but I can't seem to put my finger on it, and haven't found clear enough documentation as of yet.
Note that for this "test", I am not using Gulp or Jspm at all, just systemjs-builder for the time being (and don't plan on using jspm at all either)
Here's what my "project" looks like:
---- Project's Root
-------- index.ts // export * from './modules/index' and eventually more
-------- modules
------------ index.ts // export * from './menu/index'
------------ menu
---------------- menu.component.ts // export class
---------------- menu.service.ts // export class
I want to package this under a single file, where I will have multiple SystemRegister modules that can be consumed in an app thereafter
I tried the following without success:
var Builder = require('systemjs-builder');
// optional constructor options
// sets the baseURL and loads the configuration file
var builder = new Builder('./modules');
builder.bundle('./modules/index.ts', {
/* SystemJS Configuration Here */
baseURL: './modules',
transpiler: 'typescript',
typescriptOptions: {
"module": "system",
"emitDecoratorMetadata": true,
"experimentalDecorators": true
},
defaultExtension: 'ts',
packages: {
'modules': {
defaultExtension: 'ts'
}
}
}, 'infrastructure.js')
.then(function() {
console.log('Build complete');
})
.catch(function(err) {
console.error(err);
})
First of all, the defaultExtension options doesn't seem to work at all
So when I do import {something} from 'filePath'; (without extension), it tries to load filePath, instead of filePath.ts;
Second, if I try adding the .ts extension in my imports (which I don't want to do), it complains that the code is invalid (unexpected token #, unexpected token menuItem and so forth)
Anyone have a good example or some explanations on how this is supposed to work?
Thank you
here you have an example: angular typescript skeleton
build task looks like this:
const path = require('path');
const Builder = require('jspm').Builder;
const builder = new Builder();
const packageJson = require(path.join(config.projectDir, 'package.json'));
return beginBuild()
.then(buildSFX)
.catch((err) => console.log('Build Failed', err));
function beginBuild() {
builder.reset();
return builder.loadConfig(path.join(config.projectDir, packageJson.jspm.configFile))
}
function buildSFX() {
const appName = packageJson.name;
const distFileName = `${appName}.min.js`;
const outFile = path.join(config.distDir, distFileName);
const moduleName = 'app';
const buildConfig = {
format: 'global',
minify: true,
sourceMaps: true
};
return builder.buildStatic(moduleName, outFile, buildConfig);
}
and jspm conf looks like this:
System.config({
defaultJSExtensions: true,
transpiler: "typescript",
typescriptOptions: {
"tsconfig": "src/tsconfig.json"
},
paths: {
"github:*": "vendor/jspm_packages/github/*",
"npm:*": "vendor/jspm_packages/npm/*",
"app": "src/index"
}
/// ...
}
Why do you want to bundle typescript? Bundling is a method used for optimizing the delivery of source code to the browser. The browser doesn't know typescript, it only knows javascript (unless you do on the fly transpiling).

Use Webpack to split out a module so that it can be loaded in a WebWorker

I have a web app that I compile with webpack. One of the modules that my code uses is named table.js. Until recently, it's just been another module and has been compiled into my bundle.js file with everything else.
Now I need to run table.js in a Web Worker, so I need to pull it and its dependencies into a separate file that can be loaded both standalone and by my other modules.
At first I thought to include table.js in my webpack.config.js's entry.
var config = {
...
entry: {
app: [ './src/main.js', './src/classes/table.js' ],
vendors: [],
},
...
}
That didn't work. Then I thought to separate it out like my vendors bundle.
var config = {
/* for vendors (and other modules) we have a CDN for */
addExternal: function (name, globalVar) {
this.externals[name] = globalVar;
this.entry.vendors.push(name);
},
/* for vendors we don't have a CDN for */
addVendor: function (name, path) {
this.resolve.alias[name] = path;
this.entry.vendors.push(name);
},
addPlugin: function (plugin) {
this.plugins.push(plugin);
},
entry: {
app: [ './src/main.js' ],
vendors: [],
table: [ __dirname + '/src/classes/table.js' ]
},
plugins: [],
externals: { },
output: {
path: __dirname + '/public/dist/',
filename: 'bundle.js',
publicPath: '/dist/',
sourceMapFile: '[file].map'
},
resolve: {
alias: { 'table': './src/classes/table.js' },
extensions: [ '', '.js', '.jsx' ]
},
...
}
/* add vendors and externals */
...
config.addPlugin(new CommonsChunkPlugin('vendors', 'vendors.js'));
config.addPlugin(new CommonsChunkPlugin('table', 'table.js'));
This seems to pull Table and its dependencies into a chunk of bundle.js, 1.bundle.js. Unfortunately, then calling import Table from 'table' causes this error:
ERROR in CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk (table)
I also have a circular dependency between TableStore and Table. TableStore needs to stay in bundle.js because it shouldn't be loaded into the Web Worker. Previously, when I've needed to throw things into a separate chunk, I've done:
if (someThingNeedsRequiring) {
require.ensure([], () => {
require('something');
}
}
With the circular dependency, this doesn't seem to work.
/* table.js */
let _inWebWorker = self instanceof Window,
TableStore = null;
if (!_inWebWorker) {
require.ensure([], function() { TableStore = require('../stores/table-store'); } );
}
/* table-store.js */
import Table from 'table';
Could someone set me straight on the correct way to have my webpack.config.js and how to use my imports in my module files?
(It's been quite a while since I figured this out, and I haven't touched the project in nearly six months, so I may have missed some of the details. Comment if it's not working, and I'll try to figure out what I'm missing.)
webpack.config
It turns out there are two handy-dandy JavaScript packages for doing what I want: worker-loader and workerjs.
npm install --save workerjs worker-loader
I added this in my webpack.config.js:
var config = {
// ...
worker: {
output: {
filename: '[name].worker.js',
chunkFilename: '[name].worker.js'
}
},
// ...
}
require()
In order to specify that I want my class to be run in a WebWorker file, my require looks like:
// ecmaScript 6
import TableWorker from 'worker?name=tableRoller!path/to/table';
// ecmaScript 5
var TableWorker = require('worker?name=tableRoller!path/to/table');
TableWorker is just a variable name I used for table.js's export default class Table {...}. The name=tableRoller specifies the generated outputted [name].worker.js filename. For example, I have another WebWorker named distCalc.worker.js, so my import looks like:
import DistWorker from 'worker?name=distCalc!path/to/distWorker';
Note that in this case, distWorker only ever runs in a WebWorker, while Table is used in both my main.js entry point and my tableRoller.worker.js WebWorker file.
workerjs and worker-loader generate a new entry point file and pull in all of the dependencies of those classes. Tobias Koppers (worker-loader) and Eugene Ware (workerjs) are geniuses.
Detecting WebWorker
My _inWebWorker detection is:
let _inWebWorker = typeof Window === 'undefined';
Change output filename in your webpack.config.js file
output: {
path: __dirname + '/public/dist/',
filename: '[name].js',
publicPath: '/dist/',
sourceMapFile: '[file].map'
},
then Webpack can separate your entries with its name in dist directory.

Categories