Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CS2] Add webpack support #4501

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions Cakefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,8 @@ run = (args, callback) ->
buildParser = ->
helpers.extend global, require 'util'
require 'jison'
parser = require('./lib/coffee-script/grammar').parser.generate()
# Patch Jison’s output, until https://github.com/zaach/jison/pull/339 is accepted,
# to ensure that require('fs') is only called where it exists.
parser = parser.replace "var source = require('fs')", """
var source = '';
var fs = require('fs');
if (typeof fs !== 'undefined' && fs !== null)
source = fs"""
# We don't need moduleMain, since the parser is unlikely to be run standalone
parser = require('./lib/coffee-script/grammar').parser.generate(moduleMain: ->)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is brilliant. Thanks for solving this better than my hack.

fs.writeFileSync 'lib/coffee-script/parser.js', parser

buildExceptParser = (callback) ->
Expand Down Expand Up @@ -135,7 +129,7 @@ task 'build:browser', 'merge the built scripts into a single file for use in a b
var CoffeeScript = function() {
function require(path){ return require[path]; }
#{code}
return require['./coffee-script'];
return require['./browser'];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this? Why browser here?

Copy link
Contributor Author

@akfish akfish Apr 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for logic consistency. Since browser.js is already used as the entry for bower and webpack. It could avoid unnecessary confusion to use it for all browser related situations.

}();

if (typeof define === 'function' && define.amd) {
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "coffee-script",
"main": [
"lib/coffee-script/coffee-script.js"
"lib/coffee-script/browser.js"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine. I’ve used Bower, it’s only for frontend (browser-based) JavaScript.

],
"description": "Unfancy JavaScript",
"keywords": [
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"directories": {
"lib": "./lib/coffee-script"
},
"main": "./lib/coffee-script/coffee-script",
"main": "./lib/coffee-script/index",
"browser": "./lib/coffee-script/browser",
"bin": {
"coffee": "./bin/coffee",
"cake": "./bin/cake"
Expand Down
5 changes: 4 additions & 1 deletion src/browser.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
# `text/coffeescript` script tags, source maps via data-URLs, and so on.

CoffeeScript = require './coffee-script'
CoffeeScript.require = require
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this.

compile = CoffeeScript.compile

# Use standard JavaScript `eval` to eval code.
Expand All @@ -18,6 +17,10 @@ CoffeeScript.run = (code, options = {}) ->
options.shiftLine = on
Function(compile code, options)()

CoffeeScript.register = ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it seems like the strategy here is to declare this dummy method, and then in the Node version this gets overridden with the real method? This is confusing, to say the least.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is confusing because register method should not be available in browser environment, thus it should not be one of the abstract method. But doing so will break browser test.

Because coffee's browser test is not actually ran in a browser. It runs in node environment and the first call in the runTest method is CoffeeScript.register(). I guess this might be because there's no karma, PhantomJs nor headless chromium back then.

That's why I set it to a NOP implementation to pass the test for now. I could configure a test script that actually runs in browser with one of the methods mentioned above. But I think that should be a separate PR/issue for discussion since it requires adding more devDependencies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a separate note, I also think runScripts method in the browser.coffee could be used as browser's register implementation. It's logically sound. But it will only work if the browser tests are actually running in browser. Right now it will only break the test when trying to call it in node environment.


module.exports = CoffeeScript

# If we're not in a browser environment, we're finished with the public API.
return unless window?

Expand Down
2 changes: 1 addition & 1 deletion src/cake.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fs = require 'fs'
path = require 'path'
helpers = require './helpers'
optparse = require './optparse'
CoffeeScript = require './coffee-script'
CoffeeScript = require './'

# Register .coffee extension
CoffeeScript.register()
Expand Down
101 changes: 5 additions & 96 deletions src/coffee-script.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
# contains the main entry functions for tokenizing, parsing, and compiling
# source CoffeeScript into JavaScript.

fs = require 'fs'
vm = require 'vm'
path = require 'path'
{Lexer} = require './lexer'
{parser} = require './parser'
helpers = require './helpers'
Expand Down Expand Up @@ -156,101 +153,13 @@ exports.nodes = withPrettyErrors (source, options) ->
else
parser.parse source

# Compile and execute a string of CoffeeScript (on the server), correctly
# setting `__filename`, `__dirname`, and relative `require()`.
exports.run = (code, options = {}) ->
mainModule = require.main
# Compile and execute a string of CoffeeScript
exports.run = (code, options = {}) -> throw new Error 'Not implemented'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why export this if it just throws an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is that coffee-script.coffee will contain only platform-independent code. Any platform-specific code should be moved elsewhere. Throwing an error in those "abstract" method prevents developers from accidentally require lib/coffee-script/coffee-script and not knowing what's wrong.


# Set the filename.
mainModule.filename = process.argv[1] =
if options.filename then fs.realpathSync(options.filename) else '<anonymous>'
# Compile and evaluate a string of CoffeeScript
exports.eval = (code, options = {}) -> throw new Error 'Not implemented'

# Clear the module cache.
mainModule.moduleCache and= {}

# Assign paths for node_modules loading
dir = if options.filename?
path.dirname fs.realpathSync options.filename
else
fs.realpathSync '.'
mainModule.paths = require('module')._nodeModulePaths dir

# Compile.
if not helpers.isCoffee(mainModule.filename) or require.extensions
answer = compile code, options
code = answer.js ? answer

mainModule._compile code, mainModule.filename

# Compile and evaluate a string of CoffeeScript (in a Node.js-like environment).
# The CoffeeScript REPL uses this to run the input.
exports.eval = (code, options = {}) ->
return unless code = code.trim()
createContext = vm.Script.createContext ? vm.createContext

isContext = vm.isContext ? (ctx) ->
options.sandbox instanceof createContext().constructor

if createContext
if options.sandbox?
if isContext options.sandbox
sandbox = options.sandbox
else
sandbox = createContext()
sandbox[k] = v for own k, v of options.sandbox
sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox
else
sandbox = global
sandbox.__filename = options.filename || 'eval'
sandbox.__dirname = path.dirname sandbox.__filename
# define module/require only if they chose not to specify their own
unless sandbox isnt global or sandbox.module or sandbox.require
Module = require 'module'
sandbox.module = _module = new Module(options.modulename || 'eval')
sandbox.require = _require = (path) -> Module._load path, _module, true
_module.filename = sandbox.__filename
for r in Object.getOwnPropertyNames require when r not in ['paths', 'arguments', 'caller']
_require[r] = require[r]
# use the same hack node currently uses for their own REPL
_require.paths = _module.paths = Module._nodeModulePaths process.cwd()
_require.resolve = (request) -> Module._resolveFilename request, _module
o = {}
o[k] = v for own k, v of options
o.bare = on # ensure return value
js = compile code, o
if sandbox is global
vm.runInThisContext js
else
vm.runInContext js, sandbox

exports.register = -> require './register'

# Throw error with deprecation warning when depending upon implicit `require.extensions` registration
if require.extensions
for ext in @FILE_EXTENSIONS then do (ext) ->
require.extensions[ext] ?= ->
throw new Error """
Use CoffeeScript.register() or require the coffee-script/register module to require #{ext} files.
"""

exports._compileFile = (filename, sourceMap = no, inlineMap = no) ->
raw = fs.readFileSync filename, 'utf8'
# Strip the Unicode byte order mark, if this file begins with one.
stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw

try
answer = compile stripped, {
filename, sourceMap, inlineMap
sourceFiles: [filename]
literate: helpers.isLiterate filename
}
catch err
# As the filename and code of a dynamically loaded file will be different
# from the original file compiled with CoffeeScript.run, add that
# information to error so it can be pretty-printed later.
throw helpers.updateSyntaxError err, stripped, filename

answer
exports.register = -> throw new Error 'Not implemented'

# Instantiate a Lexer for our use here.
lexer = new Lexer
Expand Down
2 changes: 1 addition & 1 deletion src/command.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fs = require 'fs'
path = require 'path'
helpers = require './helpers'
optparse = require './optparse'
CoffeeScript = require './coffee-script'
CoffeeScript = require './'
{spawn, exec} = require 'child_process'
{EventEmitter} = require 'events'

Expand Down
108 changes: 106 additions & 2 deletions src/index.coffee
Original file line number Diff line number Diff line change
@@ -1,2 +1,106 @@
# Loader for CoffeeScript as a Node.js library.
exports[key] = val for key, val of require './coffee-script'
# Node.js Implementation
CoffeeScript = require './coffee-script'
fs = require 'fs'
vm = require 'vm'
path = require 'path'

helpers = CoffeeScript.helpers
compile = CoffeeScript.compile

# Compile and execute a string of CoffeeScript (on the server), correctly
# setting `__filename`, `__dirname`, and relative `require()`.
CoffeeScript.run = (code, options = {}) ->
mainModule = require.main

# Set the filename.
mainModule.filename = process.argv[1] =
if options.filename then fs.realpathSync(options.filename) else '<anonymous>'

# Clear the module cache.
mainModule.moduleCache and= {}

# Assign paths for node_modules loading
dir = if options.filename?
path.dirname fs.realpathSync options.filename
else
fs.realpathSync '.'
mainModule.paths = require('module')._nodeModulePaths dir

# Compile.
if not helpers.isCoffee(mainModule.filename) or require.extensions
answer = compile code, options
code = answer.js ? answer

mainModule._compile code, mainModule.filename

# Compile and evaluate a string of CoffeeScript (in a Node.js-like environment).
# The CoffeeScript REPL uses this to run the input.
CoffeeScript.eval = (code, options = {}) ->
return unless code = code.trim()
createContext = vm.Script.createContext ? vm.createContext

isContext = vm.isContext ? (ctx) ->
options.sandbox instanceof createContext().constructor

if createContext
if options.sandbox?
if isContext options.sandbox
sandbox = options.sandbox
else
sandbox = createContext()
sandbox[k] = v for own k, v of options.sandbox
sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox
else
sandbox = global
sandbox.__filename = options.filename || 'eval'
sandbox.__dirname = path.dirname sandbox.__filename
# define module/require only if they chose not to specify their own
unless sandbox isnt global or sandbox.module or sandbox.require
Module = require 'module'
sandbox.module = _module = new Module(options.modulename || 'eval')
sandbox.require = _require = (path) -> Module._load path, _module, true
_module.filename = sandbox.__filename
for r in Object.getOwnPropertyNames require when r not in ['paths', 'arguments', 'caller']
_require[r] = require[r]
# use the same hack node currently uses for their own REPL
_require.paths = _module.paths = Module._nodeModulePaths process.cwd()
_require.resolve = (request) -> Module._resolveFilename request, _module
o = {}
o[k] = v for own k, v of options
o.bare = on # ensure return value
js = compile code, o
if sandbox is global
vm.runInThisContext js
else
vm.runInContext js, sandbox

CoffeeScript.register = -> require './register'

# Throw error with deprecation warning when depending upon implicit `require.extensions` registration
if require.extensions
for ext in CoffeeScript.FILE_EXTENSIONS then do (ext) ->
require.extensions[ext] ?= ->
throw new Error """
Use CoffeeScript.register() or require the coffee-script/register module to require #{ext} files.
"""

CoffeeScript._compileFile = (filename, sourceMap = no, inlineMap = no) ->
raw = fs.readFileSync filename, 'utf8'
# Strip the Unicode byte order mark, if this file begins with one.
stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw

try
answer = compile stripped, {
filename, sourceMap, inlineMap
sourceFiles: [filename]
literate: helpers.isLiterate filename
}
catch err
# As the filename and code of a dynamically loaded file will be different
# from the original file compiled with CoffeeScript.run, add that
# information to error so it can be pretty-printed later.
throw helpers.updateSyntaxError err, stripped, filename

answer

module.exports = CoffeeScript
2 changes: 1 addition & 1 deletion src/register.coffee
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CoffeeScript = require './coffee-script'
CoffeeScript = require './'
child_process = require 'child_process'
helpers = require './helpers'
path = require 'path'
Expand Down
2 changes: 1 addition & 1 deletion src/repl.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ fs = require 'fs'
path = require 'path'
vm = require 'vm'
nodeREPL = require 'repl'
CoffeeScript = require './coffee-script'
CoffeeScript = require './'
{merge, updateSyntaxError} = require './helpers'

replDefaults =
Expand Down