Webpack breaking in IE11 - javascript

It's difficult to track this down, so thanks for bearing with me. Some users were complaining that our site was broken in IE11. The app is using nextjs 3.0.1 and webpack 2.7.0.
Debugging in development mode
I think I have an issue similar to Angular RxJs timer pausing on IE11. I'm getting an error from a reference called webpack///webpack bootstrapxxxxxxxxxx (where the x's are some numbers in hex) in IE11.
Here's the function that's causing the issue:
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {},
hot: hotCreateModule(moduleId),
parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),
children: []
};
// Execute the module function
var threw = true;
try {
modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
threw = false;
} finally {
if(threw) delete installedModules[moduleId];
}
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
The line modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId)); throws the error Unable to get property 'call' of undefined or null reference.
I imagine this is due to a missing polyfill, so I followed the advice at https://github.com/zeit/next.js/issues/1254 and added this to next.config.js (the webpack config for next):
const originalEntry = config.entry
config.entry = function () {
return originalEntry()
.then((entry) => {
Object.keys(entry).forEach(k => {
entry[k].unshift('babel-polyfill')
})
console.log(entry)
return entry
})
}
I'm still seeing the same error.
Additional details in production
One thing that's interesting is that I have a different issue in the production version of the nextjs app. It's deep in the app.js file generated by next, but the error seems to come from this line https://github.com/ianstormtaylor/heroku-logger/blob/master/src/index.js#L12:
const {
LOG_LEVEL,
NODE_ENV,
} = process.env
It's throwing Expected identifier. Is this because the module isn't getting transpiled from ES6 to ES5 correctly? There's probably an underlying issue (that I saw in development), rather than a problem with the heroku-logger library.
Realize this is a complicated issue and I'm probably missing some details. Thanks in advance for your help!

In case anyone else wrestled with this, I left the babel-polyfill in the webpack config and changed the build command to:
next build && babel .next/*.js --out-dir . --presets=es2015,react
This is pretty clunky because some code is babel-ified by webpack and then again in the output directory. Would love other suggestions!

Related

How to use the TypeScript Compiler API to type-check modules imported using `require()`?

I am using TypeChecker from the TypeScript Compiler API in order to extract (inferred) type information for every node in the AST of my program. In particular, I try to find out the return values from imported module functions such as:
var vec3 = require('gl-matrix/vec3')
var myVec = vec3.fromValues(1, 2, 3) // $ExpectedType vec3
This works well for modules that were imported using the import { … } from '…' statement, but unfortunately, modules that were imported using require() like above are not recognized correctly, I only receive the type any for them. However, I have set both compiler options allowJs and checkJs.
Why are the types form require()d modules not inferred correctly? VS Code (which AFAIK relies on the same API?) is able to infer the types from require() statements as well, so I'd guess that in general, tsc is able of handling them. Are there any other compiler options that I need to set differently? Or is this indeed not supported and I need to use some other package for this?
Here is a minimum script to reproduce, I have also put it on repl.it together with two example files: https://replit.com/#LinqLover/typecheck-js
var ts = require("typescript")
// Run `node index.js sample-import.js`` to see the working TypeScript analysis
const files = process.argv[1] != "/run_dir/interp.js" ? process.argv.slice(2) : ["sample-require.js"]
console.log(`Analyzing ${files}:`)
const program = ts.createProgram(files, {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJS,
allowJs: true,
checkJs: true
})
const checker = program.getTypeChecker()
for (const sourceFile of program.getSourceFiles()) {
if (!sourceFile.isDeclarationFile) {
ts.forEachChild(sourceFile, visit)
}
}
function visit(node) {
try {
const type = checker.getTypeAtLocation(node)
console.log(checker.typeToString(type))
} catch (e) {
// EAFP
}
ts.forEachChild(node, visit)
}
Thank you so much in advance!
For follow-up, this turned out to be an issue with the type definitions for gl-matrix. I should better have tried out multiple packages before suspecting that the TypeScript engine itself could be broken ...
gl-matrix issue: https://github.com/toji/gl-matrix/issues/429

How to tell which files are being transpiled by Babel 6?

I have a project that is using babel-register to dynamically transpile ES6 source to ES5 when requiring that module in a Node 6.6 project. I've read that babel-register hooks into Node's require function in order to transpile a file when you try to load it, but I'm not always clear on which files will be affected by that change.
This question comes up for me a lot when I'm writing tests: is only my production code getting transpiled, or does the test code get transpiled too?This brings me to the more general question, which is the topic of this post:
How can I tell when Babel is actually running, and which files are being transpiled?
Example code
Let's say I have production classes like this that are written in ES6 syntax
//src/greeter.js
export default class Greeter {
sayHello() {
return 'Hello World';
}
}
and Babel is configured to transpile as so (.babelrc)
{
"presets": ["es2015"]
}
and then there's some test code
//features/step_definitions/greeter_steps.js
import Greeter from '../../src/greeter'; //Causes greeter.js to be transpiled
import expect from 'expect';
var stepWrapper = function() {
//Does Babel try to transpile this code too?
this.Given(/^a greeter$/, function() {
this.greeter = new Greeter();
});
this.When(/^I ask it for a general greeting$/, function() {
this.greeting = this.greeter.sayHello();
});
this.Then(/^it should greet the entire world$/, function() {
expect(this.greeting).toEqual('Hello World');
});
};
module.exports = stepWrapper;
and all of that runs on Node like so
cucumberjs --compiler js:babel-core/register
Example code is available here, if that is helpful.
I made a hack to node_modules/babel-register/lib/node.js to do some logging like so
function compile(filename) {
var result = void 0;
var opts = new _babelCore.OptionManager().init((0, _extend2.default)({ sourceRoot: _path2.default.dirname(filename) }, (0, _cloneDeep2.default)(transformOpts), { filename: filename }));
var cacheKey = (0, _stringify2.default)(opts) + ":" + babel.version;
var env = process.env.BABEL_ENV || process.env.NODE_ENV;
console.log('[babel-register::compile] filename=' + filename + '\n'); //Added logging here
if (env) cacheKey += ":" + env;
if (cache) {
var cached = cache[cacheKey];
if (cached && cached.mtime === mtime(filename)) {
result = cached;
}
}
...
}
which then reports that test and production code are at least passing through Babel on some level
$ npm t
> cucumber-js-babel#1.0.0 test /Users/krull/git/sandbox/node/cucumber-js-babel
> cucumberjs --compiler js:babel-core/register
[babel-register::compile] filename=.../node/cucumber-js-babel/features/step_definitions/greeter_steps.js
[babel-register::compile] filename=.../node/cucumber-js-babel/src/greeter.js
...test results...
However, I'm hoping for a better solution that
works by some means of plugins and/or configuration, instead of monkey patching
better distinguishes which files are actually being transpiled, and which ones pass through Babel without modification
Because of this:
cucumberjs --compiler js:babel-core/register
...babel is invoked for both your test and regular source code. Keep in mind that in node, the only way to import JS is through require, so obviously babel-register will always be invoked. Of course, what babel does depends on its configuration, but most likely you have a simple configuration where all files required by require except those under node_modules will be transpiled.

Packaging-up Browser/Server CommonJS modules with dependancies

Lets say I'm writing a module in JavaScript which can be used on both the browser and the server (with Node). Lets call it Module. And lets say that that Module would benefit from methods in another module called Dependancy. Both of these modules have been written to be used by both the browser and the server, à la CommonJS style:
module.js
if (typeof module !== 'undefined' && module.exports)
module.exports = Module; /* server */
else
this.Module = Module; /* browser */
dependancy.js
if (typeof module !== 'undefined' && module.exports)
module.exports = Dependancy; /* server */
else
this.Dependancy = Dependancy; /* browser */
Obviously, Dependancy can be used straight-out-of-the-box in a browser. But if Module contains a var dependancy = require('dependency'); directive in it, it becomes more of a hassle to 'maintain' the module.
I know that I could perform a global check for Dependancy within Module, like this:
var dependancy = this.Dependancy || require('dependancy');
But that means my Module has two added requirements for browser installation:
the user must include the dependency.js file as a <script> in their document
and the user must make sure this script is loaded before module.js
Adding those two requirements throws the idea of an easy-going modular framework like CommonJS.
The other option for me is that I include a second, compiled script in my Module package with the dependency.js bundled using browserify. I then instruct users who are using the script in the browser to include this script, while server-side users use the un-bundled entry script outlined in the package.json. This is preferable to the first way, but it requires a pre-compilation process which I would have to run every time I changed the library (for example, before uploading to GitHub).
Is there any other way of doing this that I haven't thought of?
The two answers currently given are both very useful, and have helped me to arrive at my current solution. But, as per my comments, they don't quite satisfy my particular requirements of both portability vs ease-of-use (both for the client and the module maintainer).
What I found, in the end, was a particular flag in the browserify command line interface that can bundle the modules and expose them as global variables AND be used within RequireJS (if needed). Browserify (and others) call this Universal Module Definition (UMD). Some more about that here.
By passing the --standalone flag in a browserify command, I can set my module up for UMD easily.
So...
Here's the package.js for Module:
{
"name": "module",
"version": "0.0.1",
"description": "My module that requires another module (dependancy)",
"main": "index.js",
"scripts": {
"bundle": "browserify --standalone module index.js > module.js"
},
"author": "shennan",
"devDependencies": {
"dependancy": "*",
"browserify": "*"
}
}
So, when at the root of my module, I can run this in the command line:
$ npm run-script bundle
Which bundles up the dependancies into one file, and exposes them as per the UMD methodology. This means I can bootstrap the module in three different ways:
NodeJS
var Module = require('module');
/* use Module */
Browser Vanilla
<script src="module.js"></script>
<script>
var Module = module;
/* use Module */
</script>
Browser with RequireJS
<script src="require.js"></script>
<script>
requirejs(['module.js'], function (Module) {
/* use Module */
});
</script>
Thanks again for everybody's input. All of the answers are valid and I encourage everyone to try them all as different use-cases will require different solutions.
Of course you could use the same module with dependency on both sides. You just need to specify it better. This is the way I use:
(function (name, definition){
if (typeof define === 'function'){ // AMD
define(definition);
} else if (typeof module !== 'undefined' && module.exports) { // Node.js
module.exports = definition();
} else { // Browser
var theModule = definition(), global = this, old = global[name];
theModule.noConflict = function () {
global[name] = old;
return theModule;
};
global[name] = theModule;
}
})('Dependency', function () {
// return the module's API
return {
'key': 'value'
};
});
This is just a very basic sample - you can return function, instantiate function or do whatever you like. In my case I'm returning an object.
Now let's say this is the Dependency class. Your Module class should look pretty much the same, but it should have a dependency to Dependency like:
function (require, exports, module) {
var dependency = require('Dependency');
}
In RequireJS this is called Simplified CommonJS Wrapper: http://requirejs.org/docs/api.html#cjsmodule
Because there is a require statement at the beginning of your code, it will be matched as a dependency and therefore it will either be lazy loaded or if you optimize it - marked as a dependency early on (it will convert define(definition) to define(['Dependency'], definition) automatically).
The only problem here is to keep the same path to the files. Keep in mind that nested requires (if-else) won't work in Require (read the docs), so I had to do something like:
var dependency;
try {
dependency = require('./Dependency'); // node module in the same folder
} catch(err) { // it's not node
try {
dependency = require('Dependency'); // requirejs
} catch(err) { }
}
This worked perfectly for me. It's a bit tricky with all those paths, but at the end of the day, you get your two separate modules in different files which can be used on both ends without any kind of checks or hacks - they have all their dependencies are work like a charm :)
Take a look at webpack bundler.
You can write module and export it via module exports. Then You can in server use that where you have module.export and for browser build with webpack. Configuration file usage would be the best option
module.exports = {
entry: "./myModule",
output: {
path: "dist",
filename: "myModule.js",
library: "myModule",
libraryTarget: "var"
}
};
This will take myModule and export it to myModule.js file. Inside module will be assigned to var (libraryTarget flag) named myModule (library flag).
It can be exported as commonJS module, war, this, function
Since bundling is node script, this flag values can be grammatically set.
Take a look at externals flag. It is used if you want to have special behavior for some dependencies. For example you are creating react component and in your module you want to require it but not when you are bundling for web because it already is there.
Hope it is what you are looking for.

How to setup Karma + Jasmine to use ES6 modules

I'm a bit stuck on this. I have a complex stack composed of middleman, karma, jasmine, babeljs to build a static website.
Considering this is an experiment, I wanted to use ES6 with modules. Everything fine on middleman side, though, I'm having hard times setting up karma + jasmine for testing.
The main problem lies within babel: if you set it to use modules: "ignore" you have to manually use System.import for all your modules in your specs, which is something I don't want. I would like to use the ES6 syntax, but if I set modules: "system", babeljs wraps all my tests into System.register, with something like the following:
System.register(["mymodule"], function (_export) {
"use strict";
var Mymodule;
return {
setters: [function (_mymodule) {
Mymodule = _mymodule["default"];
}],
execute: function () {
console.log("I'm here!!!");
console.log(Mymodule);
describe("Mymodule", function () {
it("has version", function () {
expect(Mymodule.VERSION).toEqual("1.0.0");
});
});
}
};
});
So the tests are not automatically executed. I then created the following script to work around it (which is included after all specs are included):
basePath = "/base/spec/"
modules = []
for own fileName, fileHash of window.__karma__.files
if fileName.indexOf(basePath) is 0
isRunner = fileName.indexOf("spec_runner") >= 0
isRunner ||= fileName.indexOf("spec_helper") >= 0
unless isRunner
moduleName = fileName.replace(basePath, "")
moduleName = moduleName.replace(".js", "")
modules.push(path: fileName, name: moduleName)
mappedModules = {}
baseSpecsPath = "http://localhost:9876"
for module in modules
mappedModules[module.name] = baseSpecsPath + module.path
System.config
baseURL: "http://localhost:4567/javascripts/"
map: mappedModules
for module in modules
System.import(module.name)
This code is simple: it prepares the map configuration for SystemJS, I can correctly load modules from my app (located in http://localhost:4567) and tests wrapped in System.register (located in http://localhost:9876).
However, my tests are not run, and no error reported. Even worse, I correctly get the message logged "I'm here!!!" and Mymodule is logged in console correctly. I even tried to log the value of describe and it's correctly a Suite object. So, why on earth my tests are not run? (The it block is never run)
What solutions do I have? I'm ok with changing the setup a bit to get it working, but I want to keep the following things: Middleman, ES6 modules, no dynamic module loading (all my modules are exposed, in the end, in a single file or required with a bunch of <script> tags), jasmine
I finally solved the issue. I include this file as last one:
basePath = "/base/spec/"
modules = []
for own fileName, fileHash of window.__karma__.files
if fileName.indexOf(basePath) is 0
isRunner = fileName.indexOf("spec_runner") >= 0
isRunner ||= fileName.indexOf("spec_helper") >= 0
unless isRunner
moduleName = fileName.replace(basePath, "")
moduleName = moduleName.replace(".js", "")
modules.push(path: fileName, name: moduleName)
mappedModules = {}
baseSpecsPath = "http://localhost:9876"
specModules = []
for module in modules
mappedModules[module.name] = baseSpecsPath + module.path
specModules.push(module.name)
System.config
baseURL: "http://localhost:4567/javascripts/"
map: mappedModules
moduleImports = specModules.map (moduleName) ->
System.import(moduleName)
Promise.all(moduleImports).then ->
window.__karma__.start = window.__lastKarmaStart__
window.__lastKarmaStart__ = null
delete window.__lastKarmaStart__
window.__karma__.start()
It does the following things:
Temporary disable karma start function replacing it with an empty one
Fetches each test stored in a System.register, run System.import for all tests (waits them with Promise.all)
After all imports are completed, it attaches back __karma__start and executes it, running Jasmine

How can I have webpack skip a module if it doesn't exist

I'm trying to use WebPack to include "showdown". The problem is that showdown will require("fs") and check the return value. This makes WebPack throw an error.
It seems like it should be possible to configure Webpack to generate a shim so that call to require("fs") will return false.
Maybe one of these techniques might work: http://webpack.github.io/docs/shimming-modules.html
Here's the Showdown.js code. If I comment out this code inside the node modules directory, the problem is solved. However, there should be a better way.
//
// Automatic Extension Loading (node only):
//
if (typeof module !== 'undefind' && typeof exports !== 'undefined' && typeof require !== 'undefind') {
var fs = require('fs');
if (fs) {
// Search extensions folder
var extensions = fs.readdirSync((__dirname || '.')+'/extensions').filter(function(file){
return ~file.indexOf('.js');
}).map(function(file){
return file.replace(/\.js$/, '');
});
// Load extensions into Showdown namespace
Showdown.forEach(extensions, function(ext){
var name = stdExtName(ext);
Showdown.extensions[name] = require('./extensions/' + ext);
});
}
}
The solution was to switch to marked: https://www.npmjs.org/package/marked. The showdown library is problematic as far as modules go.
Add it to noParse e.g.
var config = {
output: {
path: buildPath,
filename: 'bundle.js'
},
module: {
noParse: [
/showdown/,
],
And webpack will assume it does not contain any useful calls to require 🌹
This issue should be fixed in showdown v >=1.0.0
This seems to be a Showdown problem rather than a webpack one. The code that requires fs is intended to only be run in a node environment. Unfortunately there are some typos in the code that checks if it's running in node (the first if statement in the code you posted, undefind instead of undefined).
There is an pull request that fixes this which hasn't been merged yet.
To be honest it looks like the Showdown library is no longer maintained (last commit November 2012) so you may be better off looking for an alternative if possible.

Categories

Resources