gfranko / amdclean Goto Github PK
View Code? Open in Web Editor NEWA build tool that converts AMD code to standard JavaScript
Home Page: http://gregfranko.com/amdclean/
License: MIT License
A build tool that converts AMD code to standard JavaScript
Home Page: http://gregfranko.com/amdclean/
License: MIT License
I've been experimenting with amdclean. I really like it, but I was having a problem with a define()
being added below require()
in the output file
// More stuff up here
(function (controller) {
window.presentable = controller;
}(controller));
define("main", function(){});
}());
See the whole thing here
Adding skipModuleInsertion: true,
to my Gruntfile stops the define()
from being added:
js: {
options: {
findNestedDependencies: true,
baseUrl: 'src',
wrap: true,
preserveLicenseComments: false,
optimize: 'none',
mainConfigFile: 'src/requireConfig.js',
include: ['main'],
out: 'dist/presentable.js',
skipModuleInsertion: true,
onBuildWrite: function( name, path, contents ) {
return require('amdclean').clean(contents);
}
}
You can see all the code at https://github.com/frederickf/presentable/tree/setUpAMDclean.
Anyway, I wasn't sure if that is a bug or if the documentation just needs to be updated or maybe I'm just missing something.
Hey, I load a library than uses AMD and it's structure is
mq/
emitter.js
deferred-emitter.js
deferred-emitter.js requires emitter.js using
var Emitter = require('./emitter');
While my code has a path to "mq" so my code requires emitter.js using:
var Emitter = require('mq/emitter');
var DeferredEmitter = require('mq/deferred-emitter');
When I pass this through r.js it calls the module 'mq/emitter' so AMDClean uses the key mq_emitter
but deferred-emitter.js code is converted to
var Emitter = amdclean['emitter'];
Which is obviously undefined
. Is it a known issue?
TL;DR
The same module is required as mq/emitter
and ./emitter
on different files and AMDClean converts that into mq_emitter
and emitter
so they are not references to the same module anymore.
require('./folder/file')
is transformed to var folder/file = ...
which causes syntax error.
Change the default wrap
AMDclean option to be:
'wrap': {
'start': ';(function() {',
'end': '}());'
}
Reason: Lot's of people are using the Require.js wrap
option and becoming confused why all modules are being declared in the global scope
I have run into issues with a library we use - backbone.validation.
Backbone.validation's relevant code is an example of a factory-style define-compatible library, and looks like this:
(function (factory) {
if (typeof exports === 'object') {
module.exports = factory(require('backbone'), require('underscore'));
} else if (typeof define === 'function' && define.amd) {
define(['backbone', 'underscore'], factory);
}
}(function (Backbone, _) {
Backbone.Validation = (function(_){/*plugin logic*/}(_));
return Backbone.Validation;
}));
When I run AMD Clean, this turns into the following:
(function (factory) {
if (typeof exports === 'object') {
module.exports = factory(HSG['backbone'], HSG['underscore']);
} else if (true) {
HSG['backbonevalidation'] = function (backbone, underscore) {
return factory();
}(HSG['backbone'], HSG['underscore']);
}
}(function (Backbone, _) {
Backbone.Validation = function (_) {/**/}(_);
When this runs, Backbone
is undefined inside the factory and everything blows up. The obvious problem is that factory() does not get passed backbone and underscore like it needs to be. I.e. this would be fine if it turned into return factory(backbone, underscore);
.
It looks like the code that searches for define() calls is forgetting to pass the parameters in to the method if it is a variable function, rather than an inline function.
Make sure that relative file dependency names are correctly cleaned.
Having an uninitialized variable causes amdclean.clean
to fail with the following error:
{ [Error: TypeError: Cannot read property 'type' of null
at Object.Utils.isRequireExpression (/Users/…/node_modules/amdclean/src/amdclean.js:122:28)
]
originalError: [TypeError: Cannot read property 'type' of null] }
The following AMD modules causes an error on line 9:
define('has',['require','exports','module'],function( require, exports, module ){
exports.all = function( subject, properties ){
if(subject === undefined || typeof subject != 'object'){
return false;
}
var i = 0,
len = properties.length,
prop; //<--- error thrown because this isn't initialized
for(; i < len; i++){
prop = properties[i];
if(!(prop in subject)){
return false;
}
}
return true;
};
});
If prop
was initialized, such as prop = '';
the build would complete successfully. Also interesting to note, if I had used return
instead of exports.all
it also would work correctly.
This issue is also replicable on the amdclean website.
Hello there. I find the AMDclean very useful, but I have a problem with one configuration option, namely prefixTransform
function.
I expected it to get "raw" module names, i.e. as they are before cleaning, so that I can plug my function to override the prefixMode
behavior. Instead, it seems to get already "normalized" module names.
The reason I want to plug my own normalization function is to ensure that module names are mapped uniquely, because neither of prefix modes does that (e.g. 'a_b/c' vs 'a/b_c'). I wanted to use a simple escaping function like this:
function (moduleName) {
return 'module_' + moduleName.replace(/\W/g, function (char) {
return '$' + char.charCodeAt(0);
});
}
The solution could be to have the prefixTransform
function applied before the default normalization, instead of after it. That way users could plug in their own normalization logic, and applying the default normalization after that would ensure that result names are valid JS identifiers even if user-supplied logic left some special characters in them.
This seems like a great alternative to having a shim loader like Almond to be included in my build. Unfortunately I seem to have run into an error:
{ [Error: TypeError: Cannot read property 'type' of undefined
at maybeBlock (/mobifyjs/node_modules/amdclean/node_modules/escodegen/escodegen.js:778:17)
]
originalError: [TypeError: Cannot read property 'type' of undefined] }
I was trying to add amdclean to this project: https://github.com/mobify/mobifyjs
Suggestions?
Consider the following example:
define('example', [], function (x) {
return function A() {var example=1};
});
amdclean will translate it into:
;(function() {
var _example_;
_example_ = function (x) {
return function A() {
var example = 1;
};
}();
}());
The underscores are not necessary in the case, and they will break name references in globalModules. After checking the code, I found the underscore will be added if the name already exists in the variableStore. That's all I know till now. Please take a look. Thanks.
If a module contains at least one dependency, the "cleaned" module always includes the outer IIFE wrapper (this should be removed).
sometimes I use the simplified CJS wrapper which needs the magic dependencies require
and exports
. It would be great if you supported this as well.
define(function(require, exports){
exports.bar = require('./bar');
});
define('foo', ['require', 'exports', './bar'], function(require, exports){
exports.bar = require('./bar');
});
var foo = function (require, exports, bar) {
exports.bar = require('./bar');
}({}, {}, bar);
which is wrong.
Make sure to not clean the optional define()
wrapper when the transformAMDChecks
option is set to true
.
Have upgraded to 1.0.0, works great. There's just one tiny nuisance: if a module definition has the 'use strict' pragma, AMDClean appears to exclude it from the 'no definitions, only one return statement' rule. I thought r.js would strip it out before passing the definition to the onBuildWrite
function, but sadly not.
I'm dealing with this in Ractive.js with a simple regex hack, and it works fine (shaves a few kb off the build which is nice!), but thought I'd raise it anyway in case you felt it was worth accommodating.
One other tiny related issue: if there are no statements in a module, it would be good if that resulted in var foo = undefined
:
// in non-IE8 builds, we stub out the 'legacy' module with a paths config pointing here:
define('legacy', function () {});
// this results in
var legacy = function () {
}();
// would be great if it was this instead
var legacy = undefined;
But that definitely qualifies as an edge case so feel free to ignore it.
if the dependency name is a reserved word the tool should generate some alias to avoid issues.
define('foo', ['./function'], function(fn){
fn.bar();
});
is converted to:
var foo = function(fn) {
fn.bar();
}(function);
which is wrong since function
is a reserved word.
I'm creating a little game framework, and with these sorts of things developers expect a global namespace that holds an API they can access. Take a look at Phaser as an example: Richard has var Phaser = {}
, so it's attached to the window, then devs can instantiate modules within that namespace, like this: var player = new Phaser.Sprite()
.
I'm using requirejs for development, but I'd like to use amdclean to strip it away and simply leave a global object containing an exposed API similar to Phaser's. AMDclean gets so close, as you can see in this snippet.
The problem is that it takes the relative path of the module, like util/Thing
and turns it into util_Thing
. Honestly, there's nothing else out there that even gets this close, but it'd be a lot nicer if there was an option that stripped out the path automatically and placed it on the declared global object, so it'd become myGlobal.Thing
instead of myGlobal.util_Thing
.
Also notice that the global isn't prepended to display_Whatever
in that linked snippet, so that's also an issue, I think?
I have too many modules to keep up a list of any kind, so making path aliases as suggested in the ReadMe is not an option for me. I understand there's danger of collisions, and while that wouldn't happen in my case, this could be a place for more control, such that a module path can be aliased away if things get too crazy (eg my/really/deep/Module
to be attached as myGlobal.deep.Module
. It'd be cool to map any path to any alias on the global! And I might be wrong, but I think r.js only takes module names, not paths, and aliases them?
Any pointers would be greatly appreciated if I'm actually doing something wrong too! Thanks for this excellent build tool!
Support the Require.js optimizer cjsTranslate
option, which will allow support for "cleaning" CommonJS module code. This is currently broken in the current AMDclean release.
Indent is not respected on multiline comments.
Example: (the first line has the correct level of indentation)
/**
* jsdoc comments
*/
it would be cool if we could copy the result generated on the live site (http://gregfranko.com/amdclean/)
tried it on Chrome and for some reason it doesn't work.
Found a slightly awkward bug just now. If you export an object (i.e. define(obj)
rather than define(factory)
), AMDClean assumes it's a factory.
Unfortunately, that's what d3 does:
if (typeof define === "function" && define.amd) {
define(d3);
}
AMDClean converts this to...
if (true) {
d3 = function () {
return d3();
}();
}
...which of course raises an error.
I can't see an obvious solution (other than always assuming that it's only a factory if it's a function declaration, which would no doubt break lots of other things). Maybe an option, like...
amdclean({
code: codeContainingD3,
objectDefinitions: {
'd3': true
}
});
...?
In the meantime I'll send a PR d3's way, try and persuade them to change define(d3)
to define(function(){return d3;})
.
Hi Greg. Thank you for your great work!
I have a problem: some popular libraries uses the 'umd' boilerplate or similar techniques to reach the maximum compatibility with different standards. But they make amdclean unusable, because of local variable inside closure.
The simplest example
(function(global) {
'use strict';
// If define && define.amd
define('a', [], function() {
return 1;
});
})(this);
define('b', ['a'], function(one) {
console.log(one);
});
will give the next output:
(function (global) {
'use strict';
var a = function () {
return 1;
}();
}(this));
var b = function (one) {
console.log(one);
}(a);
which obviously will fail.
Any ideas how to deal with this?
Hello! First of all, great work.
Is there a way to make it output comments? For my library I'm building two files, one minified and one unminified one for development. I would like to preserve the comments in the unminified one.
Reading the esprima and escodegen docs it seems like you can supply the "comment" option to both esprima and escodegen to make esprima attach the comments to the AST and make escodegen output them in the code. But it seems that before doing the escodegen step you need to manually attach the comments from the "comments" sub-object of the AST to the actual AST using .attachComments. (see estools/escodegen#85)
I'm wondering if you could make this a configuration option for amdclean?
Cheers!
Hello,
I have come across another issue following your incredibly fast fix for issue #50. Using relative paths for require's results in an undefined variable.
//a module at path app/utils/test.js
define('app/utils/test', [], function(){
return function(){
console.log('hello world!');
}
});
//intended as a package file at app/utils.js
define('app/utils', ['exports'], function(){
exports.test = require('./utils/test');
});
//another module in the same directory as app/utils/test
define('app/utils/test2',[], function(){
exports.test = require('./test');
});
;(function() {
//a module at path app/utils/test.js
var app_utils_test, app_utils, app_utils_test2;
app_utils_test = function () {
console.log('hello world!');
};
//intended as a package file at app/utils.js
app_utils = function () {
exports.test = utils_test;
}();
//another module in the same directory as app/utils/test
app_utils_test2 = function () {
exports.test = test;
}();
}());
As you can see in the below output it refers to variables utils_test
and test
where the correct reference would be app_utils_test
.
Thank you for your help and your hard work!
If you have a pair of modules that have the same parent folder/filename combination, their names will be converted to the same result:
foo/prototype/commonMethodName.js -> prototype_commonMethodName
bar/prototype/commonMethodName.js -> prototype_commonMethodName
No error is raised, it just results in one module overwriting the other. There's an error message reserved for this situation in amdclean.js but it doesn't get used anywhere.
Would it make more sense to use the entire path when normalizing module names? The minified result will look the same.
I honestly don't know if this is worth the effort, but I thought I'd flag it up anyway. Leaflet.js has a fairly normal UMD export:
// define Leaflet for Node module pattern loaders, including Browserify
if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = L;
}
// define Leaflet as an AMD module
else if ('function' == typeof define && define.amd) {
define(L);
}
// and so on...
But the minifier (they're using UglifyJS, which makes me surprised I haven't encountered this in the wild before) rewrites it as a thicket of ternary operators:
"object"==typeof module&&"object"==typeof module.exports?module.exports=o:"function"==typeof define&&define.amd&&define(o)
Right now there's utils.isAMDConditional
which looks for the AMD check inside an if
condition - should we also rewrite typeof define === 'function' ? x : y
(and the Yoda and ==
variants) as x
?
I'm trying to bundle moment.js with my project without it polluting the global scope.
I'm using amdclean to clean up all the AMD references and require in moment in the correct places throughout my bundled file.
To get moment running I need to supply the following config to requirejs:
Source: http://momentjs.com/docs/#/use-it/require-js/
require.config({
config: {
moment: {
noGlobal: true
}
}
});
How can I get this working with AMDClean?
Here is some sample code I ripped from moment.js
require.config({
config: {
moment: {
noGlobal: true
}
}
});
define('moment', function(require, exports, module) {
if(module.config && module.config() && module.config().noGlobal === true) {
// release the global variable
console.log('released');
}
return 'moment moment moment moment';
});
And this is what amdclean gives me
;(function() {
var moment;
require.config({ config: { moment: { noGlobal: true } } });
moment = function (exports) {
if (module.config && module.config() && module.config().noGlobal === true) {
// release the global variable
console.log('released');
}
return 'moment moment moment moment';
}();
}());
Hey @gfranko, I've been experimenting with dropping Almond from Backbone Boilerplate builds and I feel like this library could work excellent for my use case. A blocking problem at the moment is that third-party libraries that have been shimmed still contain the define
invocation.
Do you have any suggestions for this particular use-case?
For me is really common to name the variable of a module with the module's name
var Q = require('Q');
But in this case the web parser generates a code than clearly will not work.
var Q = Q;
If we have a var
statement with multiple declarations, such as
var x = 5,
j = 'hi',
special = {what: 0};
Then AMD clean will output it as
var x = 5, j = 'hi', special = {what: 0};
Which produces really long lines really fast, and makes using a debugger very tough.
Comments are getting removed inside of module definitions.
AMD Code
// Comment above the module declaration
define('example', function () {
// Comment inside the module declaration
var example;
});
Standard JavaScript Code
// Comment above the module declaration
var example = function () {
var example;
}();
cc: @jaridmargolin
I am working on a small experimental project having Backbone as core dependency and loading all the modules using requirejs. I really would like to build my code using amdclean but it seems that somehow it uses wrong namespaces for my global dependencies. You can test it by yourself downloading the core of my project and building the dist folder https://github.com/GianlucaGuarini/backbone-bootstrap-es6/tree/refactor/dev
Example the Backbone library is namespaced as 'window.Backbone' and not as 'backbone':
input
define("Router", ["backbone","layouts/BaseLayout"], function(Backbone, BaseLayout) {
"use strict";
return Backbone.Router.extend({});
});
output
var Router = function (Backbone, BaseLayout) {
return Backbone.Router.extend({});
}(backbone, layouts_BaseLayout);
is there any workaround to avoid this problem?
Have a look at this example to see what I mean.
The standard output for modA should instead be amdclean['modA']
. It appears there's no case for this situation. Is it possible to get this covered?
App does not start and the console shows the following error:
Uncaught TypeError: Cannot read property 'module' of undefined
Which refers to the following line:
return angular.module('lingua-web', ['ui.router']);
In context:
app = function (angular) {
return angular.module('lingua-web', ['ui.router']);
}(_angular_);
Looking at the built JS file, the angular variable is declared at the top of the file, but unlike jquery & underscore which are also declared there, angular is never assigned to.
requirejs: {
compile: {
options: {
baseUrl: "javascript",
mainConfigFile: "javascript/config/requireConfig.js",
include: ["miniApps/notebook"],
insertRequire: ["miniApps/notebook"],
out: "build/app.min.js",
optimize: "none",
skipModuleInsertion: true,
onModuleBundleComplete: function (data) {
var fs = require("fs");
var amdclean = require("amdclean");
var outputFile = data.path;
fs.writeFileSync(outputFile, amdclean.clean({
filePath: outputFile
}));
}
}
}
}
var require = {
shim: {
angular: {
exports: "angular"
},
underscore: {
exports: "_"
},
jquery: {
exports: "$"
},
"angular-ui-router": {
exports: "uiRouter",
deps: [
"angular"
]
},
"jquery-ui": {
deps: [
"jquery"
]
}
},
baseUrl: "/LinguaWeb/javascript",
packages: [
],
paths: {
angular: "../dependencies/angular/angular",
"angular-ui-router": "../dependencies/angular-ui-router/release/angular-ui-router",
"iframe-resizer": "../dependencies/iframe-resizer/js/iframeResizer.min",
jquery: "../dependencies/jquery/dist/jquery",
"jquery-ui": "../dependencies/jquery-ui/ui/jquery-ui",
requirejs: "../dependencies/requirejs/require",
underscore: "../dependencies/underscore/underscore"
}
}
Add a new removeModules
option allows you to pass an array of module names that you would like to be removed from your source. For example, if you are building your app into one file and are using the RequireJS text!
plugin, you most likely do not want the text plugin to be included in your built file, since all of your templates are already inlined in your build.
Example Showing How To Remove RequireJS Text! Plugin
amdclean.clean({
'code': '',
'removeModules': ['text']
});
I'm using jQuery 1.9.1 with bower and when I compile with amdclean, I'm getting undefined error: "jquery" (note: lowercase).
If I tweak my build script to find and replace "jquery" -> "jQuery" everything works fine.
This is my build config:
({
baseUrl: '../src',
out: '../lego.min.js',
// optimize: 'uglify2',
include: [ 'namespace' ],
// Remove license comments for almond, jquery, etc
preserveLicenseComments: false,
paths: {
'jquery': '../bower_components/jquery/jquery'
},
onBuildWrite: function (moduleName, path, contents) {
return module.require('amdclean').clean(contents);
}
})
Hi I am trying to clean a project that uses Backbone.layoutManager but amdclean does not remove all the define calls. Am I doing something wrong?
Playing around with the new requirejs config support, I discovered that it doesn't support '/' in module names, ie any modules referenced by path. The problem is that the output js passes module.name
to the module, ie using property access. So, for example, for module 'foo/bar', it looks something like:
var foo_bar = function(module){
}(module.foo/bar);
Accessing the property as an array key would fix this:
var foo_bar = function(module){
}(module['foo/bar']);
Do not remove unnecessary factory function parameters unless the aggressiveOptimizations
is set to true
. AMDclean currently strips out factory function parameters even when the aggressiveOptimizations
option is set to false
.
It'd be great if amdclean would pull out shared imports from several modules into a shared closure. For example:
define('foo', [], function() {
});
define('bar', ['foo'], function() {
});
define('baz', ['foo'], function() {
});
Would turn into something like:
(function() {
var foo = (function() {
return {}; // exports
})();
var bar = (function() {
// can use `foo` module's exports here
})();
var baz = (function() {
// can use `foo` module's exports here
})();
})();
The main objective here is to reduce output filesize by eliminating repetitive imports.
In my library I have many small modules that are used often, like this:
utils/isNumeric.js
define( function () {
'use strict';
// http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric
return function ( thing ) {
return !isNaN( parseFloat( thing ) ) && isFinite( thing );
};
});
After it's been through r.js and amdclean, it looks like this:
var utils_isNumeric = function () {
return function (thing) {
return !isNaN(parseFloat(thing)) && isFinite(thing);
};
}();
In this case, there are no dependencies, and no variable declarations - just a single return statement. So it could be rewritten like this:
var utils_isNumeric = function (thing) {
return !isNaN(parseFloat(thing)) && isFinite(thing);
};
I think this is probably quite a common case in libraries that use AMD for internal code organisation. In fact the example on http://gregfranko.com/amdclean/ is another one:
// AMD
define('example', [], function() {
return 'Convert AMD code to standard JavaScript';
});
// Standard JS
var example = function () {
return 'Convert AMD code to standard JavaScript';
}();
// but could be
var example = 'Convert AMD code to standard JavaScript';
I was wondering if it's possible to identify these cases (i.e. no dependencies, definition body is a single return statement) and discard the outer IIFE when they're encountered?
Does not find and replace not define
calls nor dependencies when generated with cjsTranslate option (not sure why does it matter since generated file from requirejs is almost the same with usage of CommonJS syntax or without it).
In your docs, there's an example about adding onBuildWrite and calling require("amdclean").clean
Well when using that in build.js and running the optimizer it throws an error about resulting undefined.
When I copy your entire source code to that event it works.
Any suggestions ?
This option would only be used if a web app was using AMDClean and the onModuleBundleComplete
RequireJS hook. This option will be identical to the RequireJS optimizer wrap option and will be a convenient way to locally scope the global object being used.
This code block:
if(typeof define === 'function' && define.amd) {
}
Is getting turned into this:
if(true) {
}
This is required for web apps to work with AMDClean, but is destructive for individual libraries that do not want the conditional AMD checks to be removed since they are trying to support AMD.
I need to turn this into a configurable option.
cc: @jaridmargolin
Your tool is interesting. Although I write all JS files into the single js file with the RequireJS optimizer within a Grunt task. Hence I would welcome a Grunt plugin for amdclean as well ...
It has been found that if a long name is being used as the globalObjectName
, then file sizes increase drastically (since the global object is being referenced throughout the built file). Make sure users understand that they should be using very short names for their "global" objects.
This occurs on the homepage. The Underscore/Lodash dependency is apparently not loaded in time, throwing the error shown in this screenshot:
Hi,
currently amdclean is a very simple code transformation utility which replaces invokations of require
and define
with standard JavaScript code. But this also means that it does not respect module dependencies in every case. Let's look at an example:
require('a', function(a) {
console.log(a);
});
define('a', function() {
return 'b';
});
Using a real AMD implementation would output 'b'
since the require
would wait for 'a'
to be defined. amdclean on the other side will convert the source to the following:
(function (a) {
console.log(a);
}(a));
var a = function () {
return 'b';
}();
Since the assignment to a
is not evaluated when the immediately invoked function expression is executed, amdclean will output undefined
.
Since we require all dependencies to be included in the source handed to amdclean.clean
we could fix that problem without additional runtime overhead by reordering the invokations. Though this will not work for circular dependencies which are sometimes used togehter with requirejs.map it will work in all other cases which use clean AMD calls.
Do you think this would be a good addition to amdclean? Since it wouldn't increase the runtime load nor the generated source file sice I think it is. I would be happy to provide a patch, if accepted.
-- ooxi
It would be nice to expose selected module to public (i.e. global object). Now if you use wrap: true then all modules stay inside a closure and you can't use them from the outside. On the other hand, using wrap: false pollutes the global space too much. In my scenario - I need only one module exposed to public - others I want to keep in a closure. Is it possible with current version of amdclean?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.