sprockets-commoner 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7be0d75114fb75a73b272d1493290b864cce8455
4
+ data.tar.gz: 3c98183d4ecfc9ab3824ece0acf091255f9df219
5
+ SHA512:
6
+ metadata.gz: 6985592443fef03121a57c8a8e1f3c37e684a2fedb0561014f8c242f2629fccf6dc2f3c30789494b4a41eececaccde49424a40b858b7a7e11ad3c204093da44e
7
+ data.tar.gz: 16d3679cb215ca29fd2c8b1513cfb09704096ab31cf368ad42e5a7c8b6a41bbd13e3344ddfc0bbd89ee9f38119428f257ba3b4ab9749dec58dd896c2d3cf9307
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ node_modules
11
+ npm-debug.log
data/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ v5.8.0
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at bouke@shopify.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # Sprockets::Commoner
2
+
3
+ `Sprockets::Commoner` is a gem that enables JavaScript package and Babel transformation in Sprockets.
4
+
5
+ ## Features
6
+
7
+ * Compile JavaScript modules into a single bundle.
8
+ * Run Babel transforms.
9
+ * Easy setup. We adhere to the Rails Way™ and don't require any additional configuration for the simplest and most common set ups.
10
+ * Integrate tightly into Sprockets/Rails. You can use any of Sprockets' features like ERB inside your files without having to jump through crazy hoops.
11
+ * Designed to emit code that compresses incredibly well. The code generated will be smaller than webpack or browserify.
12
+ * Use `process.env` inside your JavaScript. This will also run on anything in `node_modules` (e.g. Babel), to ensure dependencies are also compressed optimally.
13
+ * Automatic deduplication of Babel helpers. No need to use `babel-runtime`, as commoner will automatically detect which helpers are used and share them between modules in a way that compressed very well.
14
+
15
+ ## Setup
16
+
17
+ ### Requirements
18
+
19
+ 1. Ruby v2+.
20
+ 2. Rails/Any other application that uses Sprockets.
21
+ 2. NPM v3+. We only support version 3 because commoner doesn't do any sort of deduplication of dependencies, so you could end up with a huge bundle if you don't want out. We only test against version 3, so you will definitely run into issues when running version 2.
22
+ 3. We recommend and support version 4+ of Node.js.
23
+
24
+ ### 10-second simple set up
25
+
26
+ To get started, let's begin with the simplest possible set up: just enabling resolving of `require`.
27
+
28
+ 1. Add `sprockets-commoner` to Gemfile, run `bundle install`, and restart your Rails server.
29
+ 1. Add package.json with `babel-core` version 6 and any packages you want. For the example, we'll use the excellent [lodash](https://lodash.com) library. `npm install -S babel-core@6 lodash`
30
+ 1. `require` your client-side JavaScript packages from `application.js`!
31
+ ```
32
+ var _ = require('lodash');
33
+
34
+ console.log(_.map([1, 2, 3], function(n) { return n * 3; }));
35
+ ```
36
+
37
+ ### Enabling Babel transforms
38
+
39
+ 1. Install any Babel plugins or presets you want to use. We'll use the default ES2015 preset; `npm install babel-preset-es2015`.
40
+ 1. Add a `.babelrc` with you required configuration. We just need to do `echo '{presets: ["es2015"]}' > .babelrc`.
41
+ 1. Use any feature you want! For example, let's use `import` and arrow functions in our `application.js`:
42
+
43
+ ```
44
+ import {map} from 'lodash';
45
+
46
+ console.log(map([1, 2, 3], (n) => n * 3));
47
+ ```
48
+
49
+ ### Advanced configuration
50
+
51
+ #### Fine-tuned selection of files to process
52
+
53
+ By default, commoner will process any file under the application root directory. If you want more fine-tuned control over which files to process, you can specify which paths to include or exclude. To do so, you will need to re-register the Sprockets processor. For example:
54
+
55
+ ```
56
+ # In config/initializers/sprockets_commoner.rb
57
+ Rails.application.config.assets.configure do |env|
58
+ env.unregister_postprocessor('application/javascript', Sprockets::Commoner::Processor)
59
+ env.register_postprocessor('application/javascript', Sprockets::Commoner::Processor.new(
60
+ env.root,
61
+ # include, exclude, and babel_exclude patterns can be path prefixes or regexes.
62
+ # Explicitely list paths to include. The default is `[env.root]`
63
+ include: ['app/assets/javascripts/subdirectory'],
64
+ # List files to ignore and not process require calls or apply any Babel transforms to. Default is empty.
65
+ exclude: [/ignored/],
66
+ # Anything listed in babel_exclude has its require calls resolved, but no transforms listed in .babelrcs applied.
67
+ # Default is [/node_modules/]
68
+ babel_exclude: [/node_modules/]
69
+ ))
70
+ end
71
+ ```
72
+
73
+ ## Testing with Teaspoon
74
+
75
+ Teaspoon is not compatible with commoner by default. Under `test/demo` you can find a demo Rails application, containing a setup with Teaspoon. The key change is a custom boot partial and JavaScript file, found under `spec/javascripts/fixtures/teaspoon/suite`. Simply copy those two files under `fixtures/teaspoon/suite` in your JavaScript tests, make necessary changes to `boot.js.erb` to point to your test files, and you should be good to go.
76
+
77
+ ## CoffeeScript interoperability
78
+
79
+ Commoner is designed from the start to facilitate a transition from CoffeeScript to ES2015.
80
+
81
+ ### Importing CoffeeScript files
82
+
83
+ Any JavaScript file can `require` a CoffeeScript file, which will cause that CoffeeScript file to be scanned for a global variable reference and the `require` call to be replaced with a reference.
84
+ If we have the following two files:
85
+
86
+ ```
87
+ # file.coffee
88
+ class window.ImportantClass
89
+ ```
90
+
91
+ ```
92
+ // main.js
93
+ var klass = require('./file');
94
+
95
+ new klass();
96
+ ```
97
+
98
+ Then the second file will just be compiled down to `new window.ImportantClass()`. Importing global references works for global assignments and class definitions.
99
+
100
+ ### Expose
101
+
102
+ We have added a custom directive that makes it very easy to expose an ES2015 module to the global namespace so it can be used by CoffeeScript files or any other code. For example:
103
+
104
+ ```
105
+ 'expose window.MyClass`;
106
+
107
+ export default class MyClass {}
108
+ ```
109
+
110
+ `expose` will use the default export if available, otherwise the whole module namespace will be assigned to the global variable. For example:
111
+
112
+ ```
113
+ // constants.js
114
+ 'expose window.Constants';
115
+
116
+ export const A = 1;
117
+ export const B = 2;
118
+ export const C = 3;
119
+ ```
120
+
121
+ This will make `window.Constants` equal `{A: 1, B: 2, C: 3}`.
122
+
123
+ ## Methodology
124
+
125
+ Commoner registers a postprocessor that takes any `application/javascript` file and passes it through Babel.
126
+
127
+ ### `require` support
128
+
129
+ `Sprockets::Commoner` enables support for `require()` by replacing `require` function calls with variable references. It does this by assigning every file a unique identifier based on their filename. For example, if the file you're referencing is located at `<root>/node_modules/package/index.js` it will get the name `__commoner_module__node_modules$package$index_js`. Any file that then `require`s this specific file will have that `require` call replaced with the identifier.
130
+ reference.
131
+
132
+ The Babel plugin also communicates back any files that were required to make sure they are included by Sprockets. `Sprockets::Commoner` depends on the topological ordering that Sprockets does to make sure any module that is needed is instantiated before it is used.
133
+
134
+ ### Example
135
+
136
+ After the regular Babel plugins are done doing their thing, `babel-plugin-commoner-internal` kicks in, which is commoner's plugin that does the actual resolving. This plugin does the following things:
137
+
138
+ * It finds any `require` calls and rewires them to variable references (as detailed in [`require` support](#require-support))
139
+ * It wraps the module in a function and supplies it with `module` and `exports`. The end value of `module.exports` gets assigned to the module identifier, which is referenced by other files (as specified in '`require` support') Example:
140
+
141
+ ```
142
+ var __commoner_module__node_modules$package$index_js = __commoner_initialize_module__(function (module, exports) {
143
+ exports.default = 123;
144
+ });
145
+ ```
146
+ * If these is an expose directive at the top of the file, it assigns `module.exports` to the specified variable. For example, if the top of the file contains `'expose window.Whatever';` it will assign `exports.default` if there is a default, otherwise it just assigns `exports`. Therefore if our file has `expose window.Whatever` and no default, it will get `window.Whatever = exports;` appended to it.
147
+
148
+ #### Bundling
149
+
150
+ After all the files have been transformed, there is a bundle step which combines all of the processed JavaScript modules together. It then prepends an initializer function and all the Babel helpers (which are shared between all modules).
151
+
152
+ For example, if we have the following two files:
153
+
154
+ ```
155
+ // module.js
156
+ export default function a() {
157
+ return 1;
158
+ };
159
+ ```
160
+
161
+ ```
162
+ // application.js
163
+ import a from './module';
164
+
165
+ a();
166
+ ```
167
+
168
+ We will end up with the following (browser-runnable) file:
169
+
170
+ ```
171
+ !function() {
172
+ var __commoner_initialize_module__ = function(f) {
173
+ var module = {exports: {}};
174
+ f.call(module.exports, module, module.exports);
175
+ return module.exports;
176
+ };
177
+ var global = window;
178
+ var __commoner_helper__interopRequireDefault = function (obj) {
179
+ return obj && obj.__esModule ? obj : {
180
+ default: obj
181
+ };
182
+ };
183
+ var __commoner_module__app$assets$javascripts$module_js = __commoner_initialize_module__(function (module, exports) {
184
+ "use strict";
185
+
186
+ Object.defineProperty(exports, "__esModule", {
187
+ value: true
188
+ });
189
+ exports.default = a;
190
+ function a() {
191
+ return 1;
192
+ };
193
+ });
194
+ var __commoner_module__app$assets$javascripts$application_js = __commoner_initialize_module__(function (module, exports) {
195
+ 'use strict';
196
+
197
+ var _module2 = __commoner_helper__interopRequireDefault(__commoner_module__app$assets$javascripts$module_js);
198
+
199
+ (0, _module2.default)();
200
+ });
201
+ }();
202
+ ```
203
+
204
+ This file is meant to be compressed, and does incredibly well when processed by UglifyJS.
205
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :spec
data/circle.yml ADDED
@@ -0,0 +1,13 @@
1
+ machine:
2
+ node:
3
+ version: v5.7.0
4
+ ruby:
5
+ version: 2.2.2
6
+
7
+ dependencies:
8
+ override:
9
+ - script/setup
10
+
11
+ test:
12
+ override:
13
+ - script/test
@@ -0,0 +1,2 @@
1
+ /node_modules
2
+ npm-debug.log
@@ -0,0 +1,24 @@
1
+ # babel-plugin-commoner
2
+
3
+ ## Usage
4
+
5
+ `babel-plugin-commoner` should be included in your `.babelrc` by adding `commoner`. You can't install it through npm, but this directory is automatically added to the path by the Sprockets plugin.
6
+
7
+ ## Options
8
+
9
+ * `globals`: A mapping of module name to global variable name, which will ovveride any import of that package with a global variable reference.
10
+
11
+ ## Example
12
+
13
+ ```json
14
+ {
15
+ presets: ["es2015"],
16
+ plugins: [
17
+ ["commoner", {
18
+ globals: {
19
+ "jquery": "$"
20
+ }
21
+ }]
22
+ ]
23
+ }
24
+ ```
@@ -0,0 +1,265 @@
1
+ 'use strict';
2
+ if (typeof Object.assign != 'function') {
3
+ (function () {
4
+ Object.assign = function (target) {
5
+ 'use strict';
6
+ if (target === undefined || target === null) {
7
+ throw new TypeError('Cannot convert undefined or null to object');
8
+ }
9
+
10
+ var output = Object(target);
11
+ for (var index = 1; index < arguments.length; index++) {
12
+ var source = arguments[index];
13
+ if (source !== undefined && source !== null) {
14
+ for (var nextKey in source) {
15
+ if (source.hasOwnProperty(nextKey)) {
16
+ output[nextKey] = source[nextKey];
17
+ }
18
+ }
19
+ }
20
+ }
21
+ return output;
22
+ };
23
+ })();
24
+ }
25
+
26
+ var fs = require('fs');
27
+ var dirname = require('path').dirname;
28
+ var resolve = require('browser-resolve').sync;
29
+
30
+ module.exports = function (context) {
31
+ var t = context.types;
32
+ var exposeTemplate = context.template("$0 = exports['default'] != null ? exports['default'] : exports;");
33
+
34
+ var opts = null;
35
+ var rootRegex = null;
36
+ var identifierRegex = null;
37
+
38
+ function createIdentifierRegex() {
39
+ var globals = ['window'].concat(opts.globalNamespaces).map(function(namespace) {
40
+ return namespace + '\\.';
41
+ });
42
+
43
+ // Construct regex with prefixes which denote globals (like window.Something, @Something, or anything else in opts.globalNamespaces)
44
+ var globalObject = '(?:@|' + globals.join('|') + ')';
45
+ var validIdentifier = '[a-zA-Z][_a-zA-Z0-9]*';
46
+ // Look for identifiers that look like 'window.Something' or 'Shopify.Something'
47
+ var validAssignment = '(' + globalObject + validIdentifier + '(?:\\.' + validIdentifier + ')*)';
48
+ // Look for 'window.Something =' or 'class window.Something'
49
+ return '^(?:(?:' + validAssignment + '\\s*=)|(?:class ' + validAssignment + '))';
50
+ }
51
+
52
+ /*
53
+ * BIGGEST HACK OF __ALL_TIME__
54
+ * If we're requiring a CoffeeScript file, we're going to be assuming that we're assigning to the global namespace in that file.
55
+ * so, use a RegExp to find out that variable definition. (yep)
56
+ */
57
+ function findDeclarationInCoffeeFile(path) {
58
+ var contents = fs.readFileSync(path);
59
+ var identifiers = [];
60
+
61
+ for (var regexp = new RegExp(identifierRegex, 'gm'), find = regexp.exec(contents); find != null; find = regexp.exec(contents)) {
62
+ identifiers.push(find[1] || find[2]);
63
+ }
64
+
65
+ if (identifiers.length === 0) {
66
+ throw new Error('No identifiers found in ' + path);
67
+ } else if (identifiers.length > 1) {
68
+ throw new Error('Multiple identifiers found in ' + path);
69
+ }
70
+
71
+ return identifiers[0].replace(/^@/, 'window.');
72
+ }
73
+
74
+ function isRequire(path) {
75
+ return path.isCallExpression() && path.get('callee').isIdentifier({ name: 'require' }) && !path.scope.hasBinding('require');
76
+ }
77
+
78
+ // Get the target path from a require call
79
+ function requireTarget(path) {
80
+ var evaluate = path.get('arguments')[0].evaluate();
81
+ if (!evaluate.confident || path.node.arguments.length !== 1) {
82
+ return null;
83
+ }
84
+
85
+ var target = evaluate.value;
86
+ if (typeof target !== 'string') {
87
+ throw new Error('Invalid require call, string expected');
88
+ }
89
+ return target;
90
+ }
91
+
92
+ // Find any 'expose <name>' directive and get back the value of '<name>'
93
+ function findExpose(directives) {
94
+ var result = void 0;
95
+ for (var i = 0; i < directives.length; i++) {
96
+ if (result = /^expose ([A-Za-z\.]+)$/.exec(directives[i].value.value)) {
97
+ directives.splice(i, 1);
98
+ return result[1];
99
+ }
100
+ }
101
+ return null;
102
+ }
103
+
104
+
105
+ // Transform a path into a variable name
106
+ function pathToIdentifier(path) {
107
+ var escapedPath = path.replace(rootRegex, '').replace(/[^a-zA-Z0-9_]/g, function (match) {
108
+ if (match === '/') {
109
+ return '$';
110
+ } else {
111
+ return '_';
112
+ }
113
+ });
114
+ return '__commoner_module__' + escapedPath;
115
+ }
116
+
117
+ function resolveTarget(file, path) {
118
+ var name = void 0;
119
+ if (opts.globals != null && (name = opts.globals[path]) != null) {
120
+ return name;
121
+ } else {
122
+ var resolvedPath = resolve(path, opts);
123
+ file.metadata.required.push(resolvedPath);
124
+
125
+ // Check if the path is under sourceRoot
126
+ var root = file.opts.sourceRoot;
127
+ if (!rootRegex.test(resolvedPath)) {
128
+ throw new Error("Cannot find module '" + path + "' from '" + dirname(file.opts.filename) + "' under '" + root + "'");
129
+ }
130
+
131
+ if (/\.coffee$/.test(resolvedPath)) {
132
+ // If it's a coffee script file, look for global variable assignments
133
+ return findDeclarationInCoffeeFile(resolvedPath);
134
+ } else {
135
+ // Otherwise we just look for the module by referencing its Special Identifier™
136
+ return pathToIdentifier(resolvedPath);
137
+ }
138
+ }
139
+ }
140
+
141
+ var callRewriter = {
142
+ VariableDeclarator: function VariableDeclarator(path, state) {
143
+ var init = path.get('init');
144
+ if (!isRequire(init)) {
145
+ return;
146
+ }
147
+ var binding = path.scope.getBinding(path.node.id.name);
148
+ if (!binding.constant) {
149
+ return;
150
+ }
151
+
152
+ var target = requireTarget(init);
153
+ if (target == null) {
154
+ return;
155
+ }
156
+
157
+ var name = resolveTarget(state.file, target);
158
+ path.scope.rename(name);
159
+ path.scope.rename(path.node.id.name, name);
160
+ path.remove();
161
+ },
162
+ CallExpression: function CallExpression(path, state) {
163
+ if (!isRequire(path)) {
164
+ return;
165
+ }
166
+
167
+ var target = requireTarget(path);
168
+ if (target == null) {
169
+ return;
170
+ }
171
+
172
+ var replacement = resolveTarget(state.file, target);
173
+ switch (path.parent.type) {
174
+ case "ExpressionStatement":
175
+ // We just need to know there's a dependency, we can remove it then
176
+ path.remove();
177
+ break;
178
+ default:
179
+ // Otherwise we just look for the module by referencing its Special Identifier™
180
+ path.replaceWith(t.identifier(replacement));
181
+ break;
182
+ }
183
+ }
184
+ };
185
+
186
+ return {
187
+ pre: function pre(file) {
188
+ if (file.metadata.required == null) {
189
+ file.metadata.required = [];
190
+ }
191
+ },
192
+
193
+ visitor: {
194
+ MemberExpression: function MemberExpression(path) {
195
+ if (path.get("object").matchesPattern("process.env")) {
196
+ var key = path.toComputedKey();
197
+ if (t.isStringLiteral(key)) {
198
+ path.replaceWith(t.valueToNode(process.env[key.value]));
199
+ }
200
+ }
201
+ },
202
+ UnaryExpression: function UnaryExpression(path) {
203
+ if (!path.node.operator === 'typeof') {
204
+ return;
205
+ }
206
+
207
+ var argument = path.get('argument');
208
+ if (!path.get('argument').isIdentifier()) {
209
+ return;
210
+ }
211
+
212
+ var name = path.node.argument.name;
213
+ if (name !== 'module' && name !== 'exports') {
214
+ return;
215
+ }
216
+
217
+ if (path.scope.hasBinding(name)) {
218
+ return;
219
+ }
220
+
221
+ path.replaceWith(t.stringLiteral('object'));
222
+ },
223
+ Program: {
224
+ exit: function exit(path, state) {
225
+ // Get options from commoner-options and merge them with the options
226
+ // that were passed to this plugin in .babelrc
227
+ opts = {
228
+ globalNamespaces: [],
229
+ // We can get these from Sprockets
230
+ extensions: ['.js', '.json', '.coffee', '.js.erb', '.coffee.erb']
231
+ };
232
+
233
+ // Look for the sprockets-commoner plugin for extra options
234
+ state.file.opts.plugins.map(function (plugin) {
235
+ return plugin[1];
236
+ }).filter(function (opts) {
237
+ return opts != null && opts.__commoner_options;
238
+ }).forEach(function (plugin) {
239
+ return Object.assign(opts, plugin);
240
+ });
241
+
242
+ Object.assign(opts, state.opts, { basedir: dirname(state.file.opts.filename) });
243
+ rootRegex = new RegExp('^' + state.file.opts.sourceRoot + '/');
244
+ identifierRegex = createIdentifierRegex();
245
+
246
+ // Signal back to Sprockets that we're rewiring
247
+ state.file.metadata.commonerEnabled = true;
248
+
249
+ var node = path.node;
250
+ var identifier = pathToIdentifier(state.file.opts.filename);
251
+ var expose = findExpose(node.directives);
252
+ if (expose != null) {
253
+ node.body.push(exposeTemplate(t.identifier(expose)));
254
+ }
255
+
256
+ // Transform module to a variable assignment.
257
+ // This variable is then referenced by any dependant children.
258
+ node.body = [t.variableDeclaration('var', [t.variableDeclarator(t.identifier(identifier), t.callExpression(t.identifier('__commoner_initialize_module__'), [t.functionExpression(null, [t.identifier('module'), t.identifier('exports')], t.blockStatement(node.body, node.directives))]))])];
259
+ node.directives = [];
260
+ path.traverse(callRewriter, state);
261
+ }
262
+ }
263
+ }
264
+ };
265
+ };
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "babel-plugin-sprockets-commoner-internal",
3
+ "private": true,
4
+ "description": "Find require calls and resolve the library they're calling.",
5
+ "scripts": {
6
+ "test": "NODE_ENV=test mocha"
7
+ },
8
+ "devDependencies": {
9
+ "babel-core": "^6.2.1",
10
+ "babel-plugin-transform-es2015-arrow-functions": "^6.5.2",
11
+ "babel-plugin-transform-es2015-modules-commonjs": "^6.2.1",
12
+ "mocha": "^2.3.4"
13
+ }
14
+ }
@@ -0,0 +1,3 @@
1
+ # commoner-options
2
+
3
+ This is a dummy Babel plugin that's just used to receive options from Sprockets.
@@ -0,0 +1,7 @@
1
+ module.exports = function () {
2
+ return {
3
+ pre: function pre() {
4
+ this.opts.__commoner_options = true;
5
+ }
6
+ };
7
+ };
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "babel-plugin-commoner-options",
3
+ "description": "A bogus plugin used for receiving options from Sprockets",
4
+ "private": true
5
+ }
@@ -0,0 +1,15 @@
1
+ require 'sprockets'
2
+ require 'sprockets/commoner/processor'
3
+ require 'sprockets/commoner/bundle'
4
+
5
+ module Sprockets
6
+ module Commoner
7
+ end
8
+
9
+ register_postprocessor 'application/javascript', ::Sprockets::Commoner::Processor
10
+ register_bundle_metadata_reducer 'application/javascript', :commoner_enabled, false, :|
11
+ register_bundle_metadata_reducer 'application/javascript', :commoner_used_helpers, Set.new, :+
12
+ register_bundle_processor 'application/javascript', ::Sprockets::Commoner::Bundle
13
+ end
14
+
15
+ require 'sprockets/commoner/railtie' if defined?(Rails)
@@ -0,0 +1,57 @@
1
+ require 'schmooze'
2
+
3
+ module Sprockets
4
+ module Commoner
5
+ class Bundle < Schmooze::Base
6
+ dependencies generator: 'babel-generator.default',
7
+ babelHelpers: 'babel-helpers',
8
+ t: 'babel-types'
9
+
10
+ method :generate_helpers, <<-JS
11
+ function(helpers) {
12
+ if (helpers.length === 0) {
13
+ return '';
14
+ }
15
+ var declaration = t.variableDeclaration('var',
16
+ helpers.map(function(helper) {
17
+ return t.variableDeclarator(t.identifier('__commoner_helper__' + helper), babelHelpers.get(helper));
18
+ })
19
+ );
20
+ return generator(declaration).code;
21
+ }
22
+ JS
23
+
24
+ PRELUDE = <<-JS.freeze
25
+ !function() {
26
+ var __commoner_initialize_module__ = function(f) {
27
+ var module = {exports: {}};
28
+ f.call(module.exports, module, module.exports);
29
+ return module.exports;
30
+ };
31
+ var global = window;
32
+ JS
33
+
34
+ OUTRO = <<-JS.freeze
35
+
36
+ }();
37
+ JS
38
+ def self.instance(env)
39
+ @instance ||= new(env.root)
40
+ end
41
+
42
+ def self.call(input)
43
+ instance(input[:environment]).call(input)
44
+ end
45
+
46
+ def call(input)
47
+ return unless input[:metadata][:commoner_enabled]
48
+
49
+ used_helpers = input[:metadata][:commoner_used_helpers]
50
+ helpers = generate_helpers(used_helpers.to_a)
51
+ {
52
+ data: "#{PRELUDE}#{helpers}\n#{input[:data]}#{OUTRO}"
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,174 @@
1
+ require 'schmooze'
2
+ require 'open3'
3
+
4
+ module Sprockets
5
+ module Commoner
6
+ class Processor < Schmooze::Base
7
+ BABELRC_FILE = '.babelrc'.freeze
8
+ PACKAGE_JSON = 'package.json'.freeze
9
+ JS_PACKAGE_PATH = File.expand_path('../../../js', __dir__)
10
+ ALLOWED_EXTENSIONS = /\.js(?:\.erb)?\z/
11
+
12
+ dependencies babel: 'babel-core', commoner: 'babel-plugin-sprockets-commoner-internal'
13
+
14
+ method :version, 'function() { return [process.version, babel.version]; }'
15
+ method :transform, %q{function(code, opts, commonerOpts) {
16
+ try {
17
+ var file = new babel.File(opts);
18
+
19
+ // The actual helpers are generated in bundle.rb
20
+ file.set("helperGenerator", function(name) { return babel.types.identifier('__commoner_helper__' + name); });
21
+
22
+ var commonerPlugin = babel.OptionManager.normalisePlugin(commoner);
23
+ file.buildPluginsForOptions({plugins: [[commonerPlugin, commonerOpts]]});
24
+
25
+ return file.wrap(code, function () {
26
+ file.addCode(code);
27
+ file.parseCode(code);
28
+ return file.transform();
29
+ });
30
+ } catch (err) {
31
+ if (err.codeFrame != null) {
32
+ err.message += "\n";
33
+ err.message += err.codeFrame;
34
+ }
35
+ throw err;
36
+ }
37
+ }}
38
+ def self.instance(env)
39
+ @instance ||= new(env.root)
40
+ end
41
+
42
+ def self.call(input)
43
+ instance(input[:environment]).call(input)
44
+ end
45
+
46
+ attr_reader :include, :exclude, :babel_exclude
47
+ def initialize(root, include: [root], exclude: [], babel_exclude: [/node_modules/])
48
+ @include = include.map {|path| expand_to_root(path, root) }
49
+ @exclude = exclude.map {|path| expand_to_root(path, root) }
50
+ @babel_exclude = babel_exclude.map {|path| expand_to_root(path, root) }
51
+ super(root, 'NODE_PATH' => JS_PACKAGE_PATH)
52
+ end
53
+
54
+ def call(input)
55
+ @cache_key ||= [
56
+ self.class.name,
57
+ version,
58
+ VERSION,
59
+ ].freeze
60
+
61
+ filename = input[:filename]
62
+
63
+ return unless should_process?(filename)
64
+
65
+ @env = input[:environment]
66
+ @required = input[:metadata][:required].to_a
67
+ insertion_index = @required.index(input[:uri]) || -1
68
+ @dependencies = Set.new(input[:metadata][:dependencies])
69
+
70
+
71
+ babel_config = babelrc_data(filename)
72
+
73
+ result = input[:cache].fetch([filename, @cache_key, input[:data], babel_config]) do
74
+ transform(input[:data], options(input), paths: @env.paths)
75
+ end
76
+
77
+ if result['metadata'].has_key?('required')
78
+ result['metadata']['required'].each do |r|
79
+ asset = resolve(r, accept: input[:content_type], pipeline: :self)
80
+ @required.insert(insertion_index, asset)
81
+ end
82
+ end
83
+
84
+ {
85
+ data: result['code'],
86
+ dependencies: @dependencies,
87
+ required: Set.new(@required),
88
+
89
+ commoner_used_helpers: Set.new(input[:metadata][:commoner_used_helpers]) + result['metadata']['usedHelpers'],
90
+ commoner_enabled: input[:metadata][:commoner_enabled] | result['metadata']['commonerEnabled'],
91
+ }
92
+ end
93
+
94
+ private
95
+ def expand_to_root(path, root)
96
+ if path.is_a?(String)
97
+ File.expand_path(path, root)
98
+ else
99
+ path
100
+ end
101
+ end
102
+
103
+ def should_process?(filename)
104
+ return false unless ALLOWED_EXTENSIONS =~ filename
105
+ return false unless self.include.empty? || match_any?(self.include, filename)
106
+ return false if match_any?(self.exclude, filename)
107
+ true
108
+ end
109
+
110
+ def match_any?(patterns, filename)
111
+ patterns.any? { |pattern| pattern_match(pattern, filename) }
112
+ end
113
+
114
+ def pattern_match(pattern, filename)
115
+ if pattern.is_a?(String)
116
+ filename.start_with?(pattern)
117
+ else
118
+ pattern === filename
119
+ end
120
+ end
121
+
122
+ def babelrc_data(filename)
123
+ while filename != (filename = File.dirname(filename))
124
+ begin
125
+ name = File.join(filename, BABELRC_FILE)
126
+ data = File.read(name)
127
+ depend_on_file(name)
128
+ return data
129
+ rescue Errno::ENOENT
130
+ name = File.join(filename, PACKAGE_JSON)
131
+ data = package_babel_data(name)
132
+ if data
133
+ depend_on_file(name)
134
+ return JSON.dump(data)
135
+ else
136
+ nil
137
+ end
138
+ end
139
+ end
140
+ return nil
141
+ end
142
+
143
+ def package_babel_data(filename)
144
+ return JSON.parse(File.read(filename))['babel']
145
+ rescue Errno::ENOENT
146
+ return nil
147
+ end
148
+
149
+ def options(input)
150
+ # TODO(bouk): Fix sourcemaps. Sourcemaps are only available in Sprockets v4
151
+ {
152
+ 'ast' => false,
153
+ 'babelrc' => !match_any?(self.babel_exclude, input[:filename]),
154
+ 'filename' => input[:filename],
155
+ 'filenameRelative' => PathUtils.split_subpath(input[:load_path], input[:filename]),
156
+ 'moduleRoot' => nil,
157
+ 'sourceRoot' => @env.root,
158
+ }
159
+ end
160
+
161
+ def resolve(path, **kargs)
162
+ uri, deps = @env.resolve!(path, load_paths: [@env.root], **kargs)
163
+ @dependencies.merge(deps)
164
+ uri
165
+ end
166
+
167
+ def depend_on_file(path)
168
+ uri, deps = @env.resolve!(path, load_paths: [@env.root])
169
+ @dependencies.merge(deps)
170
+ uri
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,11 @@
1
+ module Sprockets
2
+ module Commoner
3
+ class Railtie < ::Rails::Railtie
4
+ initializer 'sprockets-commoner' do
5
+ # We need to disable debugging because otherwise Rails will include each file individually, while we need everything to be bundled up together into a single file.
6
+ config.assets.debug = false
7
+ config.assets.paths << 'node_modules'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module Sprockets
2
+ module Commoner
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sprockets/commoner/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sprockets-commoner"
8
+ spec.version = Sprockets::Commoner::VERSION
9
+ spec.authors = ["Bouke van der Bijl"]
10
+ spec.homepage = 'https://github.com/Shopify/sprockets-commoner'
11
+ spec.email = ["bouke@shopify.com"]
12
+ spec.license = 'MIT'
13
+
14
+ spec.summary = %q{Use Babel in Sprockets to compile modules for the browser}
15
+ spec.description = %q{Sprockets::Commoner uses Node.JS to compile ES2015+ files to ES5 using Babel directly from NPM, without vendoring it.}
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|script|js\/babel-plugin-sprockets-commoner-internal\/test)/}) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+ spec.add_dependency "sprockets", ">= 3", "< 4"
30
+ spec.add_dependency "schmooze", "~> 0.1.5"
31
+
32
+ spec.add_development_dependency "rake", "~> 10.0"
33
+ spec.add_development_dependency "minitest", "~> 5.0"
34
+ spec.add_development_dependency "coffee-script", "~> 2.4"
35
+ spec.add_development_dependency "uglifier", "~> 2.7"
36
+ spec.add_development_dependency "pry", "~> 0.10"
37
+ end
metadata ADDED
@@ -0,0 +1,185 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sprockets-commoner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bouke van der Bijl
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-05-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sprockets
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '4'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '4'
33
+ - !ruby/object:Gem::Dependency
34
+ name: schmooze
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.1.5
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.1.5
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '10.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '10.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: minitest
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '5.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '5.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: coffee-script
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '2.4'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '2.4'
89
+ - !ruby/object:Gem::Dependency
90
+ name: uglifier
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '2.7'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '2.7'
103
+ - !ruby/object:Gem::Dependency
104
+ name: pry
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.10'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.10'
117
+ description: Sprockets::Commoner uses Node.JS to compile ES2015+ files to ES5 using
118
+ Babel directly from NPM, without vendoring it.
119
+ email:
120
+ - bouke@shopify.com
121
+ executables: []
122
+ extensions: []
123
+ extra_rdoc_files: []
124
+ files:
125
+ - ".gitignore"
126
+ - ".nvmrc"
127
+ - CODE_OF_CONDUCT.md
128
+ - Gemfile
129
+ - README.md
130
+ - Rakefile
131
+ - circle.yml
132
+ - js/babel-plugin-sprockets-commoner-internal/.gitignore
133
+ - js/babel-plugin-sprockets-commoner-internal/README.md
134
+ - js/babel-plugin-sprockets-commoner-internal/index.js
135
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/browser-resolve/LICENSE
136
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/browser-resolve/README.md
137
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/browser-resolve/empty.js
138
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/browser-resolve/index.js
139
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/browser-resolve/package.json
140
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/resolve/LICENSE
141
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/resolve/README.md
142
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/resolve/index.js
143
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/resolve/lib/async.js
144
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/resolve/lib/caller.js
145
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/resolve/lib/core.js
146
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/resolve/lib/core.json
147
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/resolve/lib/node-modules-paths.js
148
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/resolve/lib/sync.js
149
+ - js/babel-plugin-sprockets-commoner-internal/node_modules/resolve/package.json
150
+ - js/babel-plugin-sprockets-commoner-internal/package.json
151
+ - js/babel-plugin-sprockets-commoner/README.md
152
+ - js/babel-plugin-sprockets-commoner/index.js
153
+ - js/babel-plugin-sprockets-commoner/package.json
154
+ - lib/sprockets/commoner.rb
155
+ - lib/sprockets/commoner/bundle.rb
156
+ - lib/sprockets/commoner/processor.rb
157
+ - lib/sprockets/commoner/railtie.rb
158
+ - lib/sprockets/commoner/version.rb
159
+ - sprockets-commoner.gemspec
160
+ homepage: https://github.com/Shopify/sprockets-commoner
161
+ licenses:
162
+ - MIT
163
+ metadata:
164
+ allowed_push_host: https://rubygems.org
165
+ post_install_message:
166
+ rdoc_options: []
167
+ require_paths:
168
+ - lib
169
+ required_ruby_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ required_rubygems_version: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ requirements: []
180
+ rubyforge_project:
181
+ rubygems_version: 2.5.1
182
+ signing_key:
183
+ specification_version: 4
184
+ summary: Use Babel in Sprockets to compile modules for the browser
185
+ test_files: []