From 7de41f10b4ff0f0d6b45d59bee0f166c3cfe3f9f Mon Sep 17 00:00:00 2001 From: Alex Mingoia Date: Tue, 10 May 2016 00:09:28 -0700 Subject: Refactor to compile independently of purescript-webpack-plugin. - Remove dependence on purescript-webpack-plugin - Fixes double-compilation issue by loading compiled JS instead of adding dependency. - Uses `psc-ide-server` for fast rebuilds. --- .babelrc | 1 + .gitignore | 10 +- LICENSE | 2 +- README.md | 52 ++++- bower.json | 10 - docs/PursLoader/Debug.md | 9 - docs/PursLoader/JsStringEscape.md | 9 - docs/PursLoader/Loader.md | 27 --- docs/PursLoader/LoaderRef.md | 51 ---- docs/PursLoader/LoaderUtil.md | 9 - docs/PursLoader/Options.md | 21 -- docs/PursLoader/Path.md | 27 --- docs/PursLoader/Plugin.md | 33 --- entry.js | 11 - package.json | 59 +++-- src/PursLoader/Debug.js | 12 - src/PursLoader/Debug.purs | 9 - src/PursLoader/JsStringEscape.js | 7 - src/PursLoader/JsStringEscape.purs | 3 - src/PursLoader/Loader.purs | 108 --------- src/PursLoader/LoaderRef.js | 50 ---- src/PursLoader/LoaderRef.purs | 40 ---- src/PursLoader/Path.js | 24 -- src/PursLoader/Path.purs | 14 -- src/PursLoader/Plugin.js | 14 -- src/PursLoader/Plugin.purs | 34 --- src/index.js | 465 +++++++++++++++++++++++++++++++++++++ webpack.config.js | 32 --- 28 files changed, 558 insertions(+), 585 deletions(-) create mode 100644 .babelrc delete mode 100644 bower.json delete mode 100644 docs/PursLoader/Debug.md delete mode 100644 docs/PursLoader/JsStringEscape.md delete mode 100644 docs/PursLoader/Loader.md delete mode 100644 docs/PursLoader/LoaderRef.md delete mode 100644 docs/PursLoader/LoaderUtil.md delete mode 100644 docs/PursLoader/Options.md delete mode 100644 docs/PursLoader/Path.md delete mode 100644 docs/PursLoader/Plugin.md delete mode 100644 entry.js delete mode 100644 src/PursLoader/Debug.js delete mode 100644 src/PursLoader/Debug.purs delete mode 100644 src/PursLoader/JsStringEscape.js delete mode 100644 src/PursLoader/JsStringEscape.purs delete mode 100644 src/PursLoader/Loader.purs delete mode 100644 src/PursLoader/LoaderRef.js delete mode 100644 src/PursLoader/LoaderRef.purs delete mode 100644 src/PursLoader/Path.js delete mode 100644 src/PursLoader/Path.purs delete mode 100644 src/PursLoader/Plugin.js delete mode 100644 src/PursLoader/Plugin.purs create mode 100644 src/index.js delete mode 100644 webpack.config.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..9d8d516 --- /dev/null +++ b/.babelrc @@ -0,0 +1 @@ +{ "presets": ["es2015"] } diff --git a/.gitignore b/.gitignore index 0a05586..548b3c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,5 @@ -.psci -.pulp-cache -npm-debug.log -index.json -index.js +**DS_Store* +build/ node_modules/ bower_components/ -build/ -tmp/ +/index.js diff --git a/LICENSE b/LICENSE index 05b0016..6fe3810 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015 Eric Thul +Copyright (c) 2016 Alexander Mingoia and Eric Thul Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index df92e69..ed25296 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,62 @@ > [PureScript](http://www.purescript.org) loader for [webpack](http://webpack.github.io) +- Supports hot-reloading and rebuilding of single source files +- Dead code elimination using the `bundle` option +- Colorized build output using `purescript-psa` and the `psc: "psa"` option + ## Install Install with [npm](https://npmjs.org/package/purs-loader). -This loader works in conjunction with the [PureScript webpack plugin](https://npmjs.org/package/purescript-webpack-plugin). Ensure the plugin is installed and configured accordingly. - ``` npm install purs-loader --save-dev ``` ## Example -Refer to the [purescript-webpack-example](https://github.com/ethul/purescript-webpack-example) for an example. +```javascript +const webpackConfig = { + // ... + loaders: [ + // ... + { + test: /\.purs$/, + loader: 'purs-loader', + exclude: /node_modules/, + query: { + psc: 'psa', + src: ['bower_components/purescript-*/src/**/*.purs', 'src/**/*.purs'], + ffi: ['bower_components/purescript-*/src/**/*.js', 'src/**/*.js'], + } + } + // ... + ] + // ... +} +``` + +Default options: + +```javascript +{ + psc: 'psc', + pscArgs: {}, + pscBundle: 'psc-bundle', + pscBundleArgs: {}, + pscIdeColors: false, // defaults to true if psc === 'psa' + bundleOutput: 'output/bundle.js', + bundleNamespace: 'PS', + bundle: false, + warnings: true, + output: 'output', + src: [ + path.join('src', '**', '*.purs'), + path.join('bower_components', 'purescript-*', 'src', '**', '*.purs') + ], + ffi: [ + path.join('src', '**', '*.js'), + path.join('bower_components', 'purescript-*', 'src', '**', '*.js') + ], +} +``` diff --git a/bower.json b/bower.json deleted file mode 100644 index 761c24c..0000000 --- a/bower.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "purs-loader", - "private": true, - "dependencies": { - "purescript-aff": "^0.13.0", - "purescript-foreign": "^0.7.0", - "purescript-unsafe-coerce": "~0.1.0", - "purescript-nullable": "~0.2.1" - } -} diff --git a/docs/PursLoader/Debug.md b/docs/PursLoader/Debug.md deleted file mode 100644 index 824a9f8..0000000 --- a/docs/PursLoader/Debug.md +++ /dev/null @@ -1,9 +0,0 @@ -## Module PursLoader.Debug - -#### `debug` - -``` purescript -debug :: forall eff. String -> Eff (loader :: Loader | eff) Unit -``` - - diff --git a/docs/PursLoader/JsStringEscape.md b/docs/PursLoader/JsStringEscape.md deleted file mode 100644 index 09f52aa..0000000 --- a/docs/PursLoader/JsStringEscape.md +++ /dev/null @@ -1,9 +0,0 @@ -## Module PursLoader.JsStringEscape - -#### `jsStringEscape` - -``` purescript -jsStringEscape :: String -> String -``` - - diff --git a/docs/PursLoader/Loader.md b/docs/PursLoader/Loader.md deleted file mode 100644 index d05e3b7..0000000 --- a/docs/PursLoader/Loader.md +++ /dev/null @@ -1,27 +0,0 @@ -## Module PursLoader.Loader - -#### `Effects` - -``` purescript -type Effects eff = (console :: CONSOLE, err :: EXCEPTION | eff) -``` - -#### `Effects_` - -``` purescript -type Effects_ eff = Effects (loader :: Loader | eff) -``` - -#### `loader` - -``` purescript -loader :: forall eff. LoaderRef -> String -> Eff (Effects_ eff) Unit -``` - -#### `loaderFn` - -``` purescript -loaderFn :: forall eff. Fn2 LoaderRef String (Eff (Effects_ eff) Unit) -``` - - diff --git a/docs/PursLoader/LoaderRef.md b/docs/PursLoader/LoaderRef.md deleted file mode 100644 index 917db3a..0000000 --- a/docs/PursLoader/LoaderRef.md +++ /dev/null @@ -1,51 +0,0 @@ -## Module PursLoader.LoaderRef - -#### `AsyncCallback` - -``` purescript -type AsyncCallback eff = Maybe Error -> String -> Eff (loader :: Loader | eff) Unit -``` - -#### `LoaderRef` - -``` purescript -data LoaderRef -``` - -#### `Loader` - -``` purescript -data Loader :: ! -``` - -#### `async` - -``` purescript -async :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) (Maybe Error -> String -> Eff (loader :: Loader | eff) Unit) -``` - -#### `cacheable` - -``` purescript -cacheable :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit -``` - -#### `clearDependencies` - -``` purescript -clearDependencies :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit -``` - -#### `resourcePath` - -``` purescript -resourcePath :: LoaderRef -> String -``` - -#### `addDependency` - -``` purescript -addDependency :: forall eff. LoaderRef -> String -> Eff (loader :: Loader | eff) Unit -``` - - diff --git a/docs/PursLoader/LoaderUtil.md b/docs/PursLoader/LoaderUtil.md deleted file mode 100644 index 36d6879..0000000 --- a/docs/PursLoader/LoaderUtil.md +++ /dev/null @@ -1,9 +0,0 @@ -## Module PursLoader.LoaderUtil - -#### `parseQuery` - -``` purescript -parseQuery :: String -> Foreign -``` - - diff --git a/docs/PursLoader/Options.md b/docs/PursLoader/Options.md deleted file mode 100644 index b3352fc..0000000 --- a/docs/PursLoader/Options.md +++ /dev/null @@ -1,21 +0,0 @@ -## Module PursLoader.Options - -#### `Options` - -``` purescript -newtype Options - = Options { bundleOutput :: String } -``` - -##### Instances -``` purescript -IsForeign Options -``` - -#### `runOptions` - -``` purescript -runOptions :: Options -> Options_ -``` - - diff --git a/docs/PursLoader/Path.md b/docs/PursLoader/Path.md deleted file mode 100644 index cc00436..0000000 --- a/docs/PursLoader/Path.md +++ /dev/null @@ -1,27 +0,0 @@ -## Module PursLoader.Path - -#### `relative` - -``` purescript -relative :: String -> String -> String -``` - -#### `resolve` - -``` purescript -resolve :: String -> String -``` - -#### `dirname` - -``` purescript -dirname :: String -> String -``` - -#### `joinPath` - -``` purescript -joinPath :: String -> String -> String -``` - - diff --git a/docs/PursLoader/Plugin.md b/docs/PursLoader/Plugin.md deleted file mode 100644 index 7a524da..0000000 --- a/docs/PursLoader/Plugin.md +++ /dev/null @@ -1,33 +0,0 @@ -## Module PursLoader.Plugin - -#### `Compile` - -``` purescript -type Compile eff = Nullable Error -> DependencyGraph -> Eff eff Unit -``` - -#### `Context` - -``` purescript -type Context eff = { compile :: Compile eff -> Eff eff Unit, options :: Options } -``` - -#### `Options` - -``` purescript -type Options = { bundle :: Boolean, output :: String, bundleOutput :: String } -``` - -#### `dependenciesOf` - -``` purescript -dependenciesOf :: DependencyGraph -> String -> Either Error (Array String) -``` - -#### `DependencyGraph` - -``` purescript -data DependencyGraph :: * -``` - - diff --git a/entry.js b/entry.js deleted file mode 100644 index 87f52d3..0000000 --- a/entry.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -var pursLoader = require('PursLoader.Loader'); - -function loader(source) { - var ref = this; - var result = pursLoader.loaderFn(ref, source); - return result(); -} - -module.exports = loader; diff --git a/package.json b/package.json index 6ab55ec..4cbdd72 100644 --- a/package.json +++ b/package.json @@ -1,30 +1,49 @@ { "name": "purs-loader", - "version": "0.6.0", - "description": "PureScript loader for webpack", - "license": "MIT", - "repository": "ethul/purs-loader", - "author": { - "name": "Eric Thul", - "email": "thul.eric@gmail.com" - }, - "scripts": { - "build": "npm run-script build:compile && npm run-script build:docs && npm run-script build:package", - "build:compile": "pulp build -o build --force", - "build:docs": "pulp docs", - "build:package": "webpack --progress --colors --profile --bail", - "build:watch": "pulp -w build -o build --force", - "build:json": "webpack --progress --colors --profile --bail --json > index.json", - "prepublish": "npm run-script build" - }, + "version": "1.0.0", + "description": "A webpack loader for PureScript.", + "main": "index.js", "files": [ "index.js" ], - "devDependencies": { - "webpack": "^1.8.4" + "scripts": { + "build": "babel src/index.js -o index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git://github.com/alexmingoia/purs-loader.git" + }, + "keywords": [ + "loader", + "webpack", + "purescript", + "purs-loader", + "purs-loader" + ], + "author": "Alexander C. Mingoia", + "contributors": [ + "Eric Thul" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/alexmingoia/purs-loader/issues" + }, + "homepage": "https://github.com/alexmingoia/purs-loader#readme", + "peerDependencies": { + "webpack": ">=1.0.0 <3.0.0", + "purescript": ">=0.8.0" }, "dependencies": { + "bluebird": "^3.3.5", + "chalk": "^1.1.3", "debug": "^2.2.0", - "js-string-escape": "^1.0.1" + "globby": "^4.0.0", + "loader-utils": "^0.2.14", + "promise-retry": "^1.1.0" + }, + "devDependencies": { + "babel-cli": "^6.8.0", + "babel-preset-es2015": "^6.6.0" } } diff --git a/src/PursLoader/Debug.js b/src/PursLoader/Debug.js deleted file mode 100644 index 85eca10..0000000 --- a/src/PursLoader/Debug.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -// module PursLoader.Debug - -var debug_ = require('debug')('purs-loader'); - -function debug(message) { - return function(){ - debug_(message); - }; -} -exports.debug = debug; diff --git a/src/PursLoader/Debug.purs b/src/PursLoader/Debug.purs deleted file mode 100644 index 7a02f69..0000000 --- a/src/PursLoader/Debug.purs +++ /dev/null @@ -1,9 +0,0 @@ -module PursLoader.Debug (debug) where - -import Prelude (Unit()) - -import Control.Monad.Eff (Eff()) - -import PursLoader.LoaderRef (Loader()) - -foreign import debug :: forall eff. String -> Eff (loader :: Loader | eff) Unit diff --git a/src/PursLoader/JsStringEscape.js b/src/PursLoader/JsStringEscape.js deleted file mode 100644 index ff0a1a6..0000000 --- a/src/PursLoader/JsStringEscape.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -// module PursLoader.JsStringEscape - -var jsStringEscape = require('js-string-escape'); - -exports.jsStringEscape = jsStringEscape; diff --git a/src/PursLoader/JsStringEscape.purs b/src/PursLoader/JsStringEscape.purs deleted file mode 100644 index 79590ae..0000000 --- a/src/PursLoader/JsStringEscape.purs +++ /dev/null @@ -1,3 +0,0 @@ -module PursLoader.JsStringEscape (jsStringEscape) where - -foreign import jsStringEscape :: String -> String diff --git a/src/PursLoader/Loader.purs b/src/PursLoader/Loader.purs deleted file mode 100644 index acb0993..0000000 --- a/src/PursLoader/Loader.purs +++ /dev/null @@ -1,108 +0,0 @@ -module PursLoader.Loader - ( Effects() - , Effects_() - , loader - , loaderFn - ) where - -import Prelude (Unit(), ($), (>>=), (<$>), (<*>), (++), (<<<), bind, const, id, pure, unit) - -import Control.Bind (join) -import Control.Monad.Eff (Eff(), foreachE) -import Control.Monad.Eff.Console (CONSOLE()) -import Control.Monad.Eff.Exception (EXCEPTION(), Error(), error) - -import Data.Array ((!!)) -import Data.Either (Either(..), either) -import Data.Function (Fn2(), mkFn2) -import Data.Maybe (maybe) -import Data.Nullable (toMaybe) -import Data.String.Regex (Regex(), match, noFlags, regex) - -import Unsafe.Coerce (unsafeCoerce) - -import PursLoader.Debug (debug) -import PursLoader.JsStringEscape (jsStringEscape) -import PursLoader.LoaderRef - ( AsyncCallback() - , LoaderRef() - , Loader() - , async - , cacheable - , addDependency - , resourcePath - ) -import PursLoader.Path (dirname, joinPath, relative) -import PursLoader.Plugin as Plugin - -type Effects eff = (console :: CONSOLE, err :: EXCEPTION | eff) - -type Effects_ eff = Effects (loader :: Loader | eff) - -loader :: forall eff. LoaderRef -> String -> Eff (Effects_ eff) Unit -loader ref source = do - callback <- async ref - - cacheable ref - - debug "Invoke PureScript plugin compilation" - - pluginContext.compile (compile callback) - where - pluginContext :: Plugin.Context (Effects_ eff) - pluginContext = (unsafeCoerce ref).purescriptWebpackPluginContext - - compile :: AsyncCallback (Effects eff) -> Plugin.Compile (Effects_ eff) - compile callback error' graph = do - either (const $ pure unit) (\a -> debug ("Adding PureScript dependency " ++ a)) name - - addDependency ref (resourcePath ref) - - either (const $ callback (pure fixedError) "") id - (handle <$> name <*> dependencies <*> exports) - where - fixedError :: Error - fixedError = error "PureScript compilation has failed." - - handle :: String -> Array String -> String -> Eff (Effects_ eff) Unit - handle name' deps res = do - debug ("Adding PureScript dependencies for " ++ name') - foreachE deps (addDependency ref) - debug "Generated loader result" - debug res - callback (const fixedError <$> toMaybe error') res - - exports :: Either Error String - exports = - if pluginContext.options.bundle - then bundleExport <$> name - else moduleExport <<< modulePath <$> name - where - bundleExport :: String -> String - bundleExport name' = "module.exports = require('" ++ jsStringEscape path ++ "')['" ++ name' ++ "'];" - where - path :: String - path = relative resourceDir pluginContext.options.bundleOutput - - moduleExport :: String -> String - moduleExport path = "module.exports = require('" ++ jsStringEscape path ++ "');" - - modulePath :: String -> String - modulePath = relative resourceDir <<< joinPath pluginContext.options.output - - resourceDir :: String - resourceDir = dirname (resourcePath ref) - - dependencies :: Either Error (Array String) - dependencies = Plugin.dependenciesOf graph (resourcePath ref) - - name :: Either Error String - name = - maybe (Left $ error "Failed to parse module name") Right - (join $ match re source >>= \as -> as !! 1) - where - re :: Regex - re = regex "(?:^|\\n)module\\s+([\\w\\.]+)" noFlags { ignoreCase = true } - -loaderFn :: forall eff. Fn2 LoaderRef String (Eff (Effects_ eff) Unit) -loaderFn = mkFn2 loader diff --git a/src/PursLoader/LoaderRef.js b/src/PursLoader/LoaderRef.js deleted file mode 100644 index a5d8e1f..0000000 --- a/src/PursLoader/LoaderRef.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -// module PursLoader.LoaderRef - -function asyncFn(isJust, fromMaybe, ref){ - return function(){ - var callback = ref.async(); - return function(error){ - return function(value){ - return function(){ - return isJust(error) ? callback(fromMaybe(new Error())(error)) - : callback(null, value); - }; - }; - }; - }; -} -function cacheable(ref){ - return function(){ - return ref.cacheable && ref.cacheable(); - }; -} - -function clearDependencies(ref){ - return function(){ - return ref.clearDependencies(); - }; -} - -function resourcePath(ref){ - return ref.resourcePath; -} - -function addDependency(ref){ - return function(dep){ - return function(){ - return ref.addDependency(dep); - }; - }; -} - -exports.asyncFn = asyncFn; - -exports.cacheable = cacheable; - -exports.clearDependencies = clearDependencies; - -exports.resourcePath = resourcePath; - -exports.addDependency = addDependency; diff --git a/src/PursLoader/LoaderRef.purs b/src/PursLoader/LoaderRef.purs deleted file mode 100644 index 140d94a..0000000 --- a/src/PursLoader/LoaderRef.purs +++ /dev/null @@ -1,40 +0,0 @@ -module PursLoader.LoaderRef - ( LoaderRef() - , Loader() - , AsyncCallback() - , async - , cacheable - , clearDependencies - , addDependency - , resourcePath - ) where - -import Prelude (Unit()) - -import Control.Monad.Eff (Eff()) -import Control.Monad.Eff.Exception (Error()) - -import Data.Function (Fn3(), runFn3) -import Data.Maybe (Maybe(), fromMaybe, isJust) - -type AsyncCallback eff = Maybe Error -> String -> Eff (loader :: Loader | eff) Unit - -data LoaderRef - -foreign import data Loader :: ! - -foreign import asyncFn :: forall eff. Fn3 (Maybe Error -> Boolean) - (Error -> Maybe Error -> Error) - LoaderRef - (Eff (loader :: Loader | eff) (AsyncCallback eff)) - -async :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) (Maybe Error -> String -> Eff (loader :: Loader | eff) Unit) -async ref = runFn3 asyncFn isJust fromMaybe ref - -foreign import cacheable :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit - -foreign import clearDependencies :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit - -foreign import resourcePath :: LoaderRef -> String - -foreign import addDependency :: forall eff. LoaderRef -> String -> Eff (loader :: Loader | eff) Unit diff --git a/src/PursLoader/Path.js b/src/PursLoader/Path.js deleted file mode 100644 index 878f256..0000000 --- a/src/PursLoader/Path.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict' - -// module PursLoader.Path - -var path = require('path'); - -function relative(from) { - return function(to){ - return path.relative(from, to); - }; -} -exports.relative = relative; - - -function joinPath(a) { - return function(b) { - return path.join(a, b); - }; -} -exports.joinPath = joinPath; - -exports.resolve = path.resolve; - -exports.dirname = path.dirname; diff --git a/src/PursLoader/Path.purs b/src/PursLoader/Path.purs deleted file mode 100644 index 98cad5a..0000000 --- a/src/PursLoader/Path.purs +++ /dev/null @@ -1,14 +0,0 @@ -module PursLoader.Path - ( relative - , resolve - , dirname - , joinPath - ) where - -foreign import relative :: String -> String -> String - -foreign import resolve :: String -> String - -foreign import dirname :: String -> String - -foreign import joinPath :: String -> String -> String diff --git a/src/PursLoader/Plugin.js b/src/PursLoader/Plugin.js deleted file mode 100644 index ded6df5..0000000 --- a/src/PursLoader/Plugin.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -// module PursLoader.Plugin - -function dependenciesOfFn(left, right, graph, node) { - try { - var dependencies = graph.dependenciesOf(node); - return right(dependencies); - } - catch (error) { - return left(error); - } -} -exports.dependenciesOfFn = dependenciesOfFn; diff --git a/src/PursLoader/Plugin.purs b/src/PursLoader/Plugin.purs deleted file mode 100644 index c798c83..0000000 --- a/src/PursLoader/Plugin.purs +++ /dev/null @@ -1,34 +0,0 @@ -module PursLoader.Plugin - ( Compile() - , Context() - , Options() - , DependencyGraph() - , dependenciesOf - ) where - -import Prelude (Unit()) - -import Control.Monad.Eff (Eff()) -import Control.Monad.Eff.Exception (Error()) - -import Data.Either (Either(..)) -import Data.Function (Fn4(), runFn4) -import Data.Nullable (Nullable()) - -type Compile eff = Nullable Error -> DependencyGraph -> Eff eff Unit - -type Context eff = { compile :: Compile eff -> Eff eff Unit, options :: Options } - -type Options = { bundle :: Boolean, output :: String, bundleOutput :: String } - -dependenciesOf :: DependencyGraph -> String -> Either Error (Array String) -dependenciesOf = runFn4 dependenciesOfFn Left Right - -foreign import data DependencyGraph :: * - -foreign import dependenciesOfFn - :: Fn4 (Error -> Either Error (Array String)) - (Array String -> Either Error (Array String)) - DependencyGraph - String - (Either Error (Array String)) diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..0a25ccd --- /dev/null +++ b/src/index.js @@ -0,0 +1,465 @@ +'use strict' + +const colors = require('chalk') +const debug = require('debug')('purs-loader') +const loaderUtils = require('loader-utils') +const globby = require('globby') +const Promise = require('bluebird') +const fs = Promise.promisifyAll(require('fs')) +const spawn = require('child_process').spawn +const path = require('path') +const retryPromise = require('promise-retry') + +const psModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i +const requireRegex = /require\(['"]\.\.\/([\w\.]+)['"]\)/g + +module.exports = function purescriptLoader(source, map) { + const callback = this.async() + const config = this.options + const query = loaderUtils.parseQuery(this.query) + const webpackOptions = this.options.purescriptLoader || {} + + const options = Object.assign({ + context: config.context, + psc: 'psc', + pscArgs: {}, + pscBundle: 'psc-bundle', + pscBundleArgs: {}, + pscIdeColors: webpackOptions.psc === 'psa' || query.psc === 'psa', + pscIdeArgs: {}, + bundleOutput: 'output/bundle.js', + bundleNamespace: 'PS', + bundle: false, + warnings: true, + output: 'output', + src: [ + path.join('src', '**', '*.purs'), + path.join('bower_components', 'purescript-*', 'src', '**', '*.purs') + ], + ffi: [ + path.join('src', '**', '*.js'), + path.join('bower_components', 'purescript-*', 'src', '**', '*.js') + ], + }, webpackOptions, query) + + this.cacheable && this.cacheable() + + let cache = config.purescriptLoaderCache = config.purescriptLoaderCache || { + rebuild: false, + deferred: [], + bundleModules: [], + } + + if (!config.purescriptLoaderInstalled) { + config.purescriptLoaderInstalled = true + + // invalidate loader cache when bundle is marked as invalid (in watch mode) + this._compiler.plugin('invalid', () => { + cache = config.purescriptLoaderCache = { + rebuild: true, + deferred: [], + ideServer: cache.ideServer + } + }) + + // add psc warnings to webpack compilation warnings + this._compiler.plugin('after-compile', (compilation, callback) => { + if (options.warnings && cache.warnings && cache.warnings.length) { + compilation.warnings.unshift(`PureScript compilation:\n${cache.warnings.join('')}`) + } + + if (cache.errors && cache.errors.length) { + compilation.errors.unshift(`PureScript compilation:\n${cache.errors.join('\n')}`) + } + + callback() + }) + } + + const psModuleName = match(psModuleRegex, source) + const psModule = { + name: psModuleName, + load: js => callback(null, js), + reject: error => callback(error), + srcPath: this.resourcePath, + srcDir: path.dirname(this.resourcePath), + jsPath: path.resolve(path.join(options.output, psModuleName, 'index.js')), + options: options, + cache: cache, + } + + if (options.bundle) { + cache.bundleModules.push(psModule.name) + } + + if (cache.rebuild) { + return connectIdeServer(psModule) + .then(rebuild) + .then(toJavaScript) + .then(psModule.load) + .catch(psModule.reject) + } + + if (cache.compilation && cache.compilation.length) { + return toJavaScript(psModule).then(psModule.load).catch(psModule.reject) + } + + // We need to wait for compilation to finish before the loaders run so that + // references to compiled output are valid. + cache.deferred.push(psModule) + + if (!cache.compilation) { + return compile(psModule) + .then(() => Promise.map(cache.deferred, psModule => { + if (typeof cache.ideServer === 'object') cache.ideServer.kill() + return toJavaScript(psModule).then(psModule.load) + })) + .catch(error => { + cache.deferred[0].reject(error) + cache.deferred.slice(1).forEach(psModule => psModule.reject(true)) + }) + } +} + +// The actual loader is executed *after* purescript compilation. +function toJavaScript(psModule) { + const options = psModule.options + const cache = psModule.cache + const bundlePath = path.resolve(options.bundleOutput) + const jsPath = cache.bundle ? bundlePath : psModule.jsPath + + debug('loading JavaScript for', psModule.srcPath) + + return Promise.props({ + js: fs.readFileAsync(jsPath, 'utf8'), + psModuleMap: psModuleMap(options.src, cache) + }).then(result => { + let js = '' + + if (options.bundle) { + // if bundling, return a reference to the bundle + js = 'module.exports = require("' + + path.relative(psModule.srcDir, options.bundleOutput) + + '")["' + psModule.name + '"]' + } else { + // replace require paths to output files generated by psc with paths + // to purescript sources, which are then also run through this loader. + const foreignRequire = 'require("' + path.resolve( + path.join(psModule.options.output, psModule.name, 'foreign.js') + ) + '")' + + js = result.js + .replace(requireRegex, (m, p1) => { + return 'require("' + result.psModuleMap[p1] + '")' + }) + .replace(/require\(['"]\.\/foreign['"]\)/g, foreignRequire) + } + + return js + }) +} + +function compile(psModule) { + const options = psModule.options + const cache = psModule.cache + const stderr = [] + + if (cache.compilation) return Promise.resolve(cache.compilation) + + cache.compilation = [] + cache.warnings = [] + cache.errors = [] + + + const args = dargs(Object.assign({ + _: options.src, + ffi: options.ffi, + output: options.output, + }, options.pscArgs)) + + debug('spawning compiler %s %o', options.psc, args) + + return (new Promise((resolve, reject) => { + console.log('\nCompiling PureScript...') + + const compilation = spawn(options.psc, args) + + compilation.stderr.on('data', data => stderr.push(data.toString())) + + compilation.on('close', code => { + console.log('Finished compiling PureScript.') + if (code !== 0) { + cache.compilation = cache.errors = stderr + reject(true) + } else { + cache.compilation = cache.warnings = stderr + resolve(psModule) + } + }) + })) + .then(compilerOutput => { + if (options.bundle) { + return bundle(options, cache).then(() => psModule) + } + return psModule + }) +} + +function rebuild(psModule) { + const options = psModule.options + const cache = psModule.cache + + debug('attempting rebuild with psc-ide-client %s', psModule.srcPath) + + const request = (body) => new Promise((resolve, reject) => { + const args = dargs(options.pscIdeArgs) + const ideClient = spawn('psc-ide-client', args) + + ideClient.stdout.once('data', data => { + const res = JSON.parse(data.toString()) + debug(res) + + if (!Array.isArray(res.result)) { + return res.resultType === 'success' + ? resolve(psModule) + : reject(res.result) + } + + Promise.map(res.result, (item, i) => { + debug(item) + return formatIdeResult(item, options, i, res.result.length) + }) + .then(compileMessages => { + if (res.resultType === 'error') { + cache.errors = compileMessages + reject(res.result) + } else { + cache.warnings = compileMessages + resolve(psModule) + } + }) + }) + + ideClient.stderr.once('data', data => reject(data.toString())) + + ideClient.stdin.write(JSON.stringify(body)) + ideClient.stdin.write('\n') + }) + + return request({ + command: 'rebuild', + params: { + file: psModule.srcPath, + } + }).catch(res => { + if (res.resultType === 'error') { + if (res.result.some(item => item.errorCode === 'UnknownModule')) { + console.log('Unknown module, attempting full recompile') + return compile(psModule).then(() => request({ command: 'load' })) + } + } + return Promise.resolve(psModule) + }) +} + +function formatIdeResult(result, options, index, length) { + const srcPath = path.relative(options.context, result.filename) + const pos = result.position + const fileAndPos = `${srcPath}:${pos.startLine}:${pos.startColumn}` + let numAndErr = `[${index+1}/${length} ${result.errorCode}]` + numAndErr = options.pscIdeColors ? colors.yellow(numAndErr) : numAndErr + + return fs.readFileAsync(result.filename, 'utf8').then(source => { + const lines = source.split('\n').slice(pos.startLine - 1, pos.endLine) + const endsOnNewline = pos.endColumn === 1 && pos.startLine !== pos.endLine + const up = options.pscIdeColors ? colors.red('^') : '^' + const down = options.pscIdeColors ? colors.red('v') : 'v' + let trimmed = lines.slice(0) + + if (endsOnNewline) { + lines.splice(lines.length - 1, 1) + pos.endLine = pos.endLine - 1 + pos.endColumn = lines[lines.length - 1].length || 1 + } + + // strip newlines at the end + if (endsOnNewline) { + trimmed = lines.reverse().reduce((trimmed, line, i) => { + if (i === 0 && line === '') trimmed.trimming = true + if (!trimmed.trimming) trimmed.push(line) + if (trimmed.trimming && line !== '') { + trimmed.trimming = false + trimmed.push(line) + } + return trimmed + }, []).reverse() + pos.endLine = pos.endLine - (lines.length - trimmed.length) + pos.endColumn = trimmed[trimmed.length - 1].length || 1 + } + + const spaces = ' '.repeat(String(pos.endLine).length) + let snippet = trimmed.map((line, i) => { + return ` ${pos.startLine + i} ${line}` + }).join('\n') + + if (trimmed.length === 1) { + snippet += `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}` + } else { + snippet = ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}` + snippet += `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}` + } + + return Promise.resolve( + `\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}` + ) + }) +} + +function bundle(options, cache) { + if (cache.bundle) return Promise.resolve(cache.bundle) + + const stdout = [] + const stderr = cache.bundle = [] + + const args = dargs(Object.assign({ + _: [path.join(options.output, '*', '*.js')], + output: options.bundleOutput, + namespace: options.bundleNamespace, + }, options.pscBundleArgs)) + + cache.bundleModules.forEach(name => args.push('--module', name)) + + debug('spawning bundler %s %o', options.pscBundle, args.join(' ')) + + return (new Promise((resolve, reject) => { + console.log('Bundling PureScript...') + + const compilation = spawn(options.pscBundle, args) + + compilation.stdout.on('data', data => stdout.push(data.toString())) + compilation.stderr.on('data', data => stderr.push(data.toString())) + compilation.on('close', code => { + if (code !== 0) { + cache.errors.concat(stderr) + return reject(true) + } + cache.bundle = stderr + resolve(fs.appendFileAsync('output/bundle.js', `module.exports = ${options.bundleNamespace}`)) + }) + })) +} + +// map of PS module names to their source path +function psModuleMap(globs, cache) { + if (cache.psModuleMap) return Promise.resolve(cache.psModuleMap) + + return globby(globs).then(paths => { + return Promise + .props(paths.reduce((map, file) => { + map[file] = fs.readFileAsync(file, 'utf8') + return map + }, {})) + .then(srcMap => { + cache.psModuleMap = Object.keys(srcMap).reduce((map, file) => { + const source = srcMap[file] + const psModuleName = match(psModuleRegex, source) + map[psModuleName] = path.resolve(file) + return map + }, {}) + return cache.psModuleMap + }) + }) +} + +function connectIdeServer(psModule) { + const options = psModule.options + const cache = psModule.cache + + if (cache.ideServer) return Promise.resolve(psModule) + + cache.ideServer = true + + const connect = () => new Promise((resolve, reject) => { + const args = dargs(options.pscIdeArgs) + + debug('attempting to connect to psc-ide-server', args) + + const ideClient = spawn('psc-ide-client', args) + + ideClient.stderr.on('data', data => { + debug(data.toString()) + cache.ideServer = false + reject(true) + }) + ideClient.stdout.once('data', data => { + debug(data.toString()) + if (data.toString()[0] === '{') { + const res = JSON.parse(data.toString()) + if (res.resultType === 'success') { + cache.ideServer = ideServer + resolve(psModule) + } else { + cache.ideServer = ideServer + reject(true) + } + } else { + cache.ideServer = false + reject(true) + } + }) + ideClient.stdin.resume() + ideClient.stdin.write(JSON.stringify({ command: 'load' })) + ideClient.stdin.write('\n') + }) + + const args = dargs(Object.assign({ + outputDirectory: options.output, + }, options.pscIdeArgs)) + + debug('attempting to start psc-ide-server', args) + + const ideServer = cache.ideServer = spawn('psc-ide-server', []) + ideServer.stderr.on('data', data => { + debug(data.toString()) + }) + + return retryPromise((retry, number) => { + return connect().catch(error => { + if (!cache.ideServer && number === 9) { + debug(error) + + console.log( + 'failed to connect to or start psc-ide-server, ' + + 'full compilation will occur on rebuild' + ) + + return Promise.resolve(psModule) + } + + return retry(error) + }) + }, { + retries: 9, + factor: 1, + minTimeout: 333, + maxTimeout: 333, + }) +} + +function match(regex, str) { + const matches = str.match(regex) + return matches && matches[1] +} + +function dargs(obj) { + return Object.keys(obj).reduce((args, key) => { + const arg = '--' + key.replace(/[A-Z]/g, '-$&').toLowerCase(); + const val = obj[key] + + if (key === '_') val.forEach(v => args.push(v)) + else if (Array.isArray(val)) val.forEach(v => args.push(arg, v)) + else args.push(arg, obj[key]) + + return args + }, []) +} diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index a39832f..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -var path = require('path'); - -var webpack = require('webpack'); - -var packageJson = require('./package.json'); - -var noErrorsPlugin = webpack.NoErrorsPlugin; - -var dedupePlugin = webpack.optimize.DedupePlugin; - -var config - = { cache: true - , target: 'node' - , entry: { index: './entry' } - , externals: Object.keys(packageJson.dependencies).reduce(function(b, a){ - b[a] = 'commonjs ' + a; - return b; - }, {}) - , output: { path: __dirname - , filename: '[name].js' - , libraryTarget: 'commonjs2' - } - , plugins: [ new noErrorsPlugin() - , new dedupePlugin() - ] - , resolve: { modulesDirectories: [ 'build' ] } - } - ; - -module.exports = config; -- cgit v1.2.3 From f23665b22bf96eabdfbfc95f20348c9475e85ecd Mon Sep 17 00:00:00 2001 From: Alex Mingoia Date: Tue, 10 May 2016 22:04:44 -0700 Subject: Recompile on when psc-client-ide receives UnknownModule. --- src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 0a25ccd..17951d3 100644 --- a/src/index.js +++ b/src/index.js @@ -222,7 +222,7 @@ function rebuild(psModule) { if (!Array.isArray(res.result)) { return res.resultType === 'success' ? resolve(psModule) - : reject(res.result) + : reject(res) } Promise.map(res.result, (item, i) => { @@ -232,7 +232,7 @@ function rebuild(psModule) { .then(compileMessages => { if (res.resultType === 'error') { cache.errors = compileMessages - reject(res.result) + reject(res) } else { cache.warnings = compileMessages resolve(psModule) -- cgit v1.2.3 From 5163b5a276920e80e71051266696e0d8a3908037 Mon Sep 17 00:00:00 2001 From: Alex Mingoia Date: Thu, 12 May 2016 13:57:55 -0700 Subject: Disable instant psc-ide rebuilds by default. Using psc-ide-server is experimental and there may be bugs or edge-cases. --- README.md | 8 ++++++++ src/index.js | 34 ++++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ed25296..e7e0b45 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ const webpackConfig = { } ``` +### Options + Default options: ```javascript @@ -45,6 +47,7 @@ Default options: pscArgs: {}, pscBundle: 'psc-bundle', pscBundleArgs: {}, + pscIde: false, // instant rebuilds using psc-ide-server (experimental) pscIdeColors: false, // defaults to true if psc === 'psa' bundleOutput: 'output/bundle.js', bundleNamespace: 'PS', @@ -61,3 +64,8 @@ Default options: ], } ``` + +### Instant rebuilds (experimental) + +Experimental support for instant rebuilds using `psc-ide-server` can be enabled +via the `pscIde: true` option. diff --git a/src/index.js b/src/index.js index 17951d3..9db8a63 100644 --- a/src/index.js +++ b/src/index.js @@ -25,6 +25,7 @@ module.exports = function purescriptLoader(source, map) { pscArgs: {}, pscBundle: 'psc-bundle', pscBundleArgs: {}, + pscIde: false, pscIdeColors: webpackOptions.psc === 'psa' || query.psc === 'psa', pscIdeArgs: {}, bundleOutput: 'output/bundle.js', @@ -56,7 +57,7 @@ module.exports = function purescriptLoader(source, map) { // invalidate loader cache when bundle is marked as invalid (in watch mode) this._compiler.plugin('invalid', () => { cache = config.purescriptLoaderCache = { - rebuild: true, + rebuild: options.pscIde, deferred: [], ideServer: cache.ideServer } @@ -216,13 +217,19 @@ function rebuild(psModule) { const ideClient = spawn('psc-ide-client', args) ideClient.stdout.once('data', data => { - const res = JSON.parse(data.toString()) - debug(res) + let res = null - if (!Array.isArray(res.result)) { + try { + res = JSON.parse(data.toString()) + debug(res) + } catch (err) { + return reject(err) + } + + if (res && !Array.isArray(res.result)) { return res.resultType === 'success' ? resolve(psModule) - : reject(res) + : reject('psc-ide rebuild failed') } Promise.map(res.result, (item, i) => { @@ -231,8 +238,15 @@ function rebuild(psModule) { }) .then(compileMessages => { if (res.resultType === 'error') { + if (res.result.some(item => item.errorCode === 'UnknownModule')) { + console.log('Unknown module, attempting full recompile') + return compile(psModule) + .then(() => request({ command: 'load' })) + .then(resolve) + .catch(() => reject('psc-ide rebuild failed')) + } cache.errors = compileMessages - reject(res) + reject('psc-ide rebuild failed') } else { cache.warnings = compileMessages resolve(psModule) @@ -251,14 +265,6 @@ function rebuild(psModule) { params: { file: psModule.srcPath, } - }).catch(res => { - if (res.resultType === 'error') { - if (res.result.some(item => item.errorCode === 'UnknownModule')) { - console.log('Unknown module, attempting full recompile') - return compile(psModule).then(() => request({ command: 'load' })) - } - } - return Promise.resolve(psModule) }) } -- cgit v1.2.3 From 0da4ad1982dec8fd03a9b5a3f56a5e4be43376c3 Mon Sep 17 00:00:00 2001 From: Alex Mingoia Date: Fri, 13 May 2016 22:35:10 -0700 Subject: Add babelified source. --- .gitignore | 1 - index.js | 472 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 472 insertions(+), 1 deletion(-) create mode 100644 index.js diff --git a/.gitignore b/.gitignore index 548b3c7..af15f97 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ build/ node_modules/ bower_components/ -/index.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..b06aed0 --- /dev/null +++ b/index.js @@ -0,0 +1,472 @@ +'use strict'; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var colors = require('chalk'); +var debug = require('debug')('purs-loader'); +var loaderUtils = require('loader-utils'); +var globby = require('globby'); +var Promise = require('bluebird'); +var fs = Promise.promisifyAll(require('fs')); +var spawn = require('child_process').spawn; +var path = require('path'); +var retryPromise = require('promise-retry'); + +var psModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i; +var requireRegex = /require\(['"]\.\.\/([\w\.]+)['"]\)/g; + +module.exports = function purescriptLoader(source, map) { + var callback = this.async(); + var config = this.options; + var query = loaderUtils.parseQuery(this.query); + var webpackOptions = this.options.purescriptLoader || {}; + + var options = Object.assign({ + context: config.context, + psc: 'psc', + pscArgs: {}, + pscBundle: 'psc-bundle', + pscBundleArgs: {}, + pscIde: false, + pscIdeColors: webpackOptions.psc === 'psa' || query.psc === 'psa', + pscIdeArgs: {}, + bundleOutput: 'output/bundle.js', + bundleNamespace: 'PS', + bundle: false, + warnings: true, + output: 'output', + src: [path.join('src', '**', '*.purs'), path.join('bower_components', 'purescript-*', 'src', '**', '*.purs')], + ffi: [path.join('src', '**', '*.js'), path.join('bower_components', 'purescript-*', 'src', '**', '*.js')] + }, webpackOptions, query); + + this.cacheable && this.cacheable(); + + var cache = config.purescriptLoaderCache = config.purescriptLoaderCache || { + rebuild: false, + deferred: [], + bundleModules: [] + }; + + if (!config.purescriptLoaderInstalled) { + config.purescriptLoaderInstalled = true; + + // invalidate loader cache when bundle is marked as invalid (in watch mode) + this._compiler.plugin('invalid', function () { + cache = config.purescriptLoaderCache = { + rebuild: options.pscIde, + deferred: [], + ideServer: cache.ideServer + }; + }); + + // add psc warnings to webpack compilation warnings + this._compiler.plugin('after-compile', function (compilation, callback) { + if (options.warnings && cache.warnings && cache.warnings.length) { + compilation.warnings.unshift('PureScript compilation:\n' + cache.warnings.join('')); + } + + if (cache.errors && cache.errors.length) { + compilation.errors.unshift('PureScript compilation:\n' + cache.errors.join('\n')); + } + + callback(); + }); + } + + var psModuleName = match(psModuleRegex, source); + var psModule = { + name: psModuleName, + load: function load(js) { + return callback(null, js); + }, + reject: function reject(error) { + return callback(error); + }, + srcPath: this.resourcePath, + srcDir: path.dirname(this.resourcePath), + jsPath: path.resolve(path.join(options.output, psModuleName, 'index.js')), + options: options, + cache: cache + }; + + if (options.bundle) { + cache.bundleModules.push(psModule.name); + } + + if (cache.rebuild) { + return connectIdeServer(psModule).then(rebuild).then(toJavaScript).then(psModule.load).catch(psModule.reject); + } + + if (cache.compilation && cache.compilation.length) { + return toJavaScript(psModule).then(psModule.load).catch(psModule.reject); + } + + // We need to wait for compilation to finish before the loaders run so that + // references to compiled output are valid. + cache.deferred.push(psModule); + + if (!cache.compilation) { + return compile(psModule).then(function () { + return Promise.map(cache.deferred, function (psModule) { + if (_typeof(cache.ideServer) === 'object') cache.ideServer.kill(); + return toJavaScript(psModule).then(psModule.load); + }); + }).catch(function (error) { + cache.deferred[0].reject(error); + cache.deferred.slice(1).forEach(function (psModule) { + return psModule.reject(true); + }); + }); + } +}; + +// The actual loader is executed *after* purescript compilation. +function toJavaScript(psModule) { + var options = psModule.options; + var cache = psModule.cache; + var bundlePath = path.resolve(options.bundleOutput); + var jsPath = cache.bundle ? bundlePath : psModule.jsPath; + + debug('loading JavaScript for', psModule.srcPath); + + return Promise.props({ + js: fs.readFileAsync(jsPath, 'utf8'), + psModuleMap: psModuleMap(options.src, cache) + }).then(function (result) { + var js = ''; + + if (options.bundle) { + // if bundling, return a reference to the bundle + js = 'module.exports = require("' + path.relative(psModule.srcDir, options.bundleOutput) + '")["' + psModule.name + '"]'; + } else { + // replace require paths to output files generated by psc with paths + // to purescript sources, which are then also run through this loader. + var foreignRequire = 'require("' + path.resolve(path.join(psModule.options.output, psModule.name, 'foreign.js')) + '")'; + + js = result.js.replace(requireRegex, function (m, p1) { + return 'require("' + result.psModuleMap[p1] + '")'; + }).replace(/require\(['"]\.\/foreign['"]\)/g, foreignRequire); + } + + return js; + }); +} + +function compile(psModule) { + var options = psModule.options; + var cache = psModule.cache; + var stderr = []; + + if (cache.compilation) return Promise.resolve(cache.compilation); + + cache.compilation = []; + cache.warnings = []; + cache.errors = []; + + var args = dargs(Object.assign({ + _: options.src, + ffi: options.ffi, + output: options.output + }, options.pscArgs)); + + debug('spawning compiler %s %o', options.psc, args); + + return new Promise(function (resolve, reject) { + console.log('\nCompiling PureScript...'); + + var compilation = spawn(options.psc, args); + + compilation.stderr.on('data', function (data) { + return stderr.push(data.toString()); + }); + + compilation.on('close', function (code) { + console.log('Finished compiling PureScript.'); + if (code !== 0) { + cache.compilation = cache.errors = stderr; + reject(true); + } else { + cache.compilation = cache.warnings = stderr; + resolve(psModule); + } + }); + }).then(function (compilerOutput) { + if (options.bundle) { + return bundle(options, cache).then(function () { + return psModule; + }); + } + return psModule; + }); +} + +function rebuild(psModule) { + var options = psModule.options; + var cache = psModule.cache; + + debug('attempting rebuild with psc-ide-client %s', psModule.srcPath); + + var request = function request(body) { + return new Promise(function (resolve, reject) { + var args = dargs(options.pscIdeArgs); + var ideClient = spawn('psc-ide-client', args); + + ideClient.stdout.once('data', function (data) { + var res = null; + + try { + res = JSON.parse(data.toString()); + debug(res); + } catch (err) { + return reject(err); + } + + if (res && !Array.isArray(res.result)) { + return res.resultType === 'success' ? resolve(psModule) : reject('psc-ide rebuild failed'); + } + + Promise.map(res.result, function (item, i) { + debug(item); + return formatIdeResult(item, options, i, res.result.length); + }).then(function (compileMessages) { + if (res.resultType === 'error') { + if (res.result.some(function (item) { + return item.errorCode === 'UnknownModule'; + })) { + console.log('Unknown module, attempting full recompile'); + return compile(psModule).then(function () { + return request({ command: 'load' }); + }).then(resolve).catch(function () { + return reject('psc-ide rebuild failed'); + }); + } + cache.errors = compileMessages; + reject('psc-ide rebuild failed'); + } else { + cache.warnings = compileMessages; + resolve(psModule); + } + }); + }); + + ideClient.stderr.once('data', function (data) { + return reject(data.toString()); + }); + + ideClient.stdin.write(JSON.stringify(body)); + ideClient.stdin.write('\n'); + }); + }; + + return request({ + command: 'rebuild', + params: { + file: psModule.srcPath + } + }); +} + +function formatIdeResult(result, options, index, length) { + var srcPath = path.relative(options.context, result.filename); + var pos = result.position; + var fileAndPos = srcPath + ':' + pos.startLine + ':' + pos.startColumn; + var numAndErr = '[' + (index + 1) + '/' + length + ' ' + result.errorCode + ']'; + numAndErr = options.pscIdeColors ? colors.yellow(numAndErr) : numAndErr; + + return fs.readFileAsync(result.filename, 'utf8').then(function (source) { + var lines = source.split('\n').slice(pos.startLine - 1, pos.endLine); + var endsOnNewline = pos.endColumn === 1 && pos.startLine !== pos.endLine; + var up = options.pscIdeColors ? colors.red('^') : '^'; + var down = options.pscIdeColors ? colors.red('v') : 'v'; + var trimmed = lines.slice(0); + + if (endsOnNewline) { + lines.splice(lines.length - 1, 1); + pos.endLine = pos.endLine - 1; + pos.endColumn = lines[lines.length - 1].length || 1; + } + + // strip newlines at the end + if (endsOnNewline) { + trimmed = lines.reverse().reduce(function (trimmed, line, i) { + if (i === 0 && line === '') trimmed.trimming = true; + if (!trimmed.trimming) trimmed.push(line); + if (trimmed.trimming && line !== '') { + trimmed.trimming = false; + trimmed.push(line); + } + return trimmed; + }, []).reverse(); + pos.endLine = pos.endLine - (lines.length - trimmed.length); + pos.endColumn = trimmed[trimmed.length - 1].length || 1; + } + + var spaces = ' '.repeat(String(pos.endLine).length); + var snippet = trimmed.map(function (line, i) { + return ' ' + (pos.startLine + i) + ' ' + line; + }).join('\n'); + + if (trimmed.length === 1) { + snippet += '\n ' + spaces + ' ' + ' '.repeat(pos.startColumn - 1) + up.repeat(pos.endColumn - pos.startColumn + 1); + } else { + snippet = ' ' + spaces + ' ' + ' '.repeat(pos.startColumn - 1) + down + '\n' + snippet; + snippet += '\n ' + spaces + ' ' + ' '.repeat(pos.endColumn - 1) + up; + } + + return Promise.resolve('\n' + numAndErr + ' ' + fileAndPos + '\n\n' + snippet + '\n\n' + result.message); + }); +} + +function bundle(options, cache) { + if (cache.bundle) return Promise.resolve(cache.bundle); + + var stdout = []; + var stderr = cache.bundle = []; + + var args = dargs(Object.assign({ + _: [path.join(options.output, '*', '*.js')], + output: options.bundleOutput, + namespace: options.bundleNamespace + }, options.pscBundleArgs)); + + cache.bundleModules.forEach(function (name) { + return args.push('--module', name); + }); + + debug('spawning bundler %s %o', options.pscBundle, args.join(' ')); + + return new Promise(function (resolve, reject) { + console.log('Bundling PureScript...'); + + var compilation = spawn(options.pscBundle, args); + + compilation.stdout.on('data', function (data) { + return stdout.push(data.toString()); + }); + compilation.stderr.on('data', function (data) { + return stderr.push(data.toString()); + }); + compilation.on('close', function (code) { + if (code !== 0) { + cache.errors.concat(stderr); + return reject(true); + } + cache.bundle = stderr; + resolve(fs.appendFileAsync('output/bundle.js', 'module.exports = ' + options.bundleNamespace)); + }); + }); +} + +// map of PS module names to their source path +function psModuleMap(globs, cache) { + if (cache.psModuleMap) return Promise.resolve(cache.psModuleMap); + + return globby(globs).then(function (paths) { + return Promise.props(paths.reduce(function (map, file) { + map[file] = fs.readFileAsync(file, 'utf8'); + return map; + }, {})).then(function (srcMap) { + cache.psModuleMap = Object.keys(srcMap).reduce(function (map, file) { + var source = srcMap[file]; + var psModuleName = match(psModuleRegex, source); + map[psModuleName] = path.resolve(file); + return map; + }, {}); + return cache.psModuleMap; + }); + }); +} + +function connectIdeServer(psModule) { + var options = psModule.options; + var cache = psModule.cache; + + if (cache.ideServer) return Promise.resolve(psModule); + + cache.ideServer = true; + + var connect = function connect() { + return new Promise(function (resolve, reject) { + var args = dargs(options.pscIdeArgs); + + debug('attempting to connect to psc-ide-server', args); + + var ideClient = spawn('psc-ide-client', args); + + ideClient.stderr.on('data', function (data) { + debug(data.toString()); + cache.ideServer = false; + reject(true); + }); + ideClient.stdout.once('data', function (data) { + debug(data.toString()); + if (data.toString()[0] === '{') { + var res = JSON.parse(data.toString()); + if (res.resultType === 'success') { + cache.ideServer = ideServer; + resolve(psModule); + } else { + cache.ideServer = ideServer; + reject(true); + } + } else { + cache.ideServer = false; + reject(true); + } + }); + ideClient.stdin.resume(); + ideClient.stdin.write(JSON.stringify({ command: 'load' })); + ideClient.stdin.write('\n'); + }); + }; + + var args = dargs(Object.assign({ + outputDirectory: options.output + }, options.pscIdeArgs)); + + debug('attempting to start psc-ide-server', args); + + var ideServer = cache.ideServer = spawn('psc-ide-server', []); + ideServer.stderr.on('data', function (data) { + debug(data.toString()); + }); + + return retryPromise(function (retry, number) { + return connect().catch(function (error) { + if (!cache.ideServer && number === 9) { + debug(error); + + console.log('failed to connect to or start psc-ide-server, ' + 'full compilation will occur on rebuild'); + + return Promise.resolve(psModule); + } + + return retry(error); + }); + }, { + retries: 9, + factor: 1, + minTimeout: 333, + maxTimeout: 333 + }); +} + +function match(regex, str) { + var matches = str.match(regex); + return matches && matches[1]; +} + +function dargs(obj) { + return Object.keys(obj).reduce(function (args, key) { + var arg = '--' + key.replace(/[A-Z]/g, '-$&').toLowerCase(); + var val = obj[key]; + + if (key === '_') val.forEach(function (v) { + return args.push(v); + });else if (Array.isArray(val)) val.forEach(function (v) { + return args.push(arg, v); + });else args.push(arg, obj[key]); + + return args; + }, []); +} -- cgit v1.2.3 From 678884960a53c8c60bbfdfd6389e08580a9ad03e Mon Sep 17 00:00:00 2001 From: Alex Mingoia Date: Sat, 14 May 2016 13:55:04 -0700 Subject: Use more descriptive names for compile state. --- src/index.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/index.js b/src/index.js index 9db8a63..26b0d81 100644 --- a/src/index.js +++ b/src/index.js @@ -89,6 +89,8 @@ module.exports = function purescriptLoader(source, map) { cache: cache, } + debug('loader called', psModule.name) + if (options.bundle) { cache.bundleModules.push(psModule.name) } @@ -101,7 +103,7 @@ module.exports = function purescriptLoader(source, map) { .catch(psModule.reject) } - if (cache.compilation && cache.compilation.length) { + if (cache.compilationFinished) { return toJavaScript(psModule).then(psModule.load).catch(psModule.reject) } @@ -109,7 +111,7 @@ module.exports = function purescriptLoader(source, map) { // references to compiled output are valid. cache.deferred.push(psModule) - if (!cache.compilation) { + if (!cache.compilationStarted) { return compile(psModule) .then(() => Promise.map(cache.deferred, psModule => { if (typeof cache.ideServer === 'object') cache.ideServer.kill() @@ -129,7 +131,7 @@ function toJavaScript(psModule) { const bundlePath = path.resolve(options.bundleOutput) const jsPath = cache.bundle ? bundlePath : psModule.jsPath - debug('loading JavaScript for', psModule.srcPath) + debug('loading JavaScript for', psModule.name) return Promise.props({ js: fs.readFileAsync(jsPath, 'utf8'), @@ -165,12 +167,9 @@ function compile(psModule) { const cache = psModule.cache const stderr = [] - if (cache.compilation) return Promise.resolve(cache.compilation) - - cache.compilation = [] - cache.warnings = [] - cache.errors = [] + if (cache.compilationStarted) return Promise.resolve(psModule) + cache.compilationStarted = true const args = dargs(Object.assign({ _: options.src, @@ -189,6 +188,7 @@ function compile(psModule) { compilation.on('close', code => { console.log('Finished compiling PureScript.') + cache.compilationFinished = true if (code !== 0) { cache.compilation = cache.errors = stderr reject(true) -- cgit v1.2.3 From 9dc1021066dea00eb43574e705df383728e667e2 Mon Sep 17 00:00:00 2001 From: Alex Mingoia Date: Sat, 14 May 2016 13:55:24 -0700 Subject: Do not assume line-delimited psc output. --- src/index.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/index.js b/src/index.js index 26b0d81..23a99dd 100644 --- a/src/index.js +++ b/src/index.js @@ -65,12 +65,12 @@ module.exports = function purescriptLoader(source, map) { // add psc warnings to webpack compilation warnings this._compiler.plugin('after-compile', (compilation, callback) => { - if (options.warnings && cache.warnings && cache.warnings.length) { - compilation.warnings.unshift(`PureScript compilation:\n${cache.warnings.join('')}`) + if (options.warnings && cache.warnings) { + compilation.warnings.unshift(`PureScript compilation:\n${cache.warnings}`) } - if (cache.errors && cache.errors.length) { - compilation.errors.unshift(`PureScript compilation:\n${cache.errors.join('\n')}`) + if (cache.errors) { + compilation.errors.unshift(`PureScript compilation:\n${cache.errors}`) } callback() @@ -184,16 +184,17 @@ function compile(psModule) { const compilation = spawn(options.psc, args) + compilation.stdout.on('data', data => stderr.push(data.toString())) compilation.stderr.on('data', data => stderr.push(data.toString())) compilation.on('close', code => { console.log('Finished compiling PureScript.') cache.compilationFinished = true if (code !== 0) { - cache.compilation = cache.errors = stderr + cache.errors = stderr.join('') reject(true) } else { - cache.compilation = cache.warnings = stderr + cache.warnings = stderr.join('') resolve(psModule) } }) @@ -245,10 +246,10 @@ function rebuild(psModule) { .then(resolve) .catch(() => reject('psc-ide rebuild failed')) } - cache.errors = compileMessages + cache.errors = compileMessages.join('\n') reject('psc-ide rebuild failed') } else { - cache.warnings = compileMessages + cache.warnings = compileMessages.join('\n') resolve(psModule) } }) @@ -346,7 +347,7 @@ function bundle(options, cache) { compilation.stderr.on('data', data => stderr.push(data.toString())) compilation.on('close', code => { if (code !== 0) { - cache.errors.concat(stderr) + cache.errors = (cache.errors || '') + stderr.join('') return reject(true) } cache.bundle = stderr -- cgit v1.2.3 From 2d052df7f972f2a5fadf1711b13263a2e4dd6d33 Mon Sep 17 00:00:00 2001 From: Alex Mingoia Date: Sat, 14 May 2016 13:56:14 -0700 Subject: Correctly parse boolean options. --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 23a99dd..e09d247 100644 --- a/src/index.js +++ b/src/index.js @@ -467,6 +467,6 @@ function dargs(obj) { else if (Array.isArray(val)) val.forEach(v => args.push(arg, v)) else args.push(arg, obj[key]) - return args + return args.filter(arg => (typeof arg !== 'boolean')) }, []) } -- cgit v1.2.3 From 63ec415a5cd01655b199f5705010788463a2b22a Mon Sep 17 00:00:00 2001 From: Alex Mingoia Date: Sat, 14 May 2016 13:57:05 -0700 Subject: Update babelified source. --- index.js | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index b06aed0..5f41361 100644 --- a/index.js +++ b/index.js @@ -61,12 +61,12 @@ module.exports = function purescriptLoader(source, map) { // add psc warnings to webpack compilation warnings this._compiler.plugin('after-compile', function (compilation, callback) { - if (options.warnings && cache.warnings && cache.warnings.length) { - compilation.warnings.unshift('PureScript compilation:\n' + cache.warnings.join('')); + if (options.warnings && cache.warnings) { + compilation.warnings.unshift('PureScript compilation:\n' + cache.warnings); } - if (cache.errors && cache.errors.length) { - compilation.errors.unshift('PureScript compilation:\n' + cache.errors.join('\n')); + if (cache.errors) { + compilation.errors.unshift('PureScript compilation:\n' + cache.errors); } callback(); @@ -89,6 +89,8 @@ module.exports = function purescriptLoader(source, map) { cache: cache }; + debug('loader called', psModule.name); + if (options.bundle) { cache.bundleModules.push(psModule.name); } @@ -97,7 +99,7 @@ module.exports = function purescriptLoader(source, map) { return connectIdeServer(psModule).then(rebuild).then(toJavaScript).then(psModule.load).catch(psModule.reject); } - if (cache.compilation && cache.compilation.length) { + if (cache.compilationFinished) { return toJavaScript(psModule).then(psModule.load).catch(psModule.reject); } @@ -105,7 +107,7 @@ module.exports = function purescriptLoader(source, map) { // references to compiled output are valid. cache.deferred.push(psModule); - if (!cache.compilation) { + if (!cache.compilationStarted) { return compile(psModule).then(function () { return Promise.map(cache.deferred, function (psModule) { if (_typeof(cache.ideServer) === 'object') cache.ideServer.kill(); @@ -127,7 +129,7 @@ function toJavaScript(psModule) { var bundlePath = path.resolve(options.bundleOutput); var jsPath = cache.bundle ? bundlePath : psModule.jsPath; - debug('loading JavaScript for', psModule.srcPath); + debug('loading JavaScript for', psModule.name); return Promise.props({ js: fs.readFileAsync(jsPath, 'utf8'), @@ -157,11 +159,9 @@ function compile(psModule) { var cache = psModule.cache; var stderr = []; - if (cache.compilation) return Promise.resolve(cache.compilation); + if (cache.compilationStarted) return Promise.resolve(psModule); - cache.compilation = []; - cache.warnings = []; - cache.errors = []; + cache.compilationStarted = true; var args = dargs(Object.assign({ _: options.src, @@ -176,17 +176,21 @@ function compile(psModule) { var compilation = spawn(options.psc, args); + compilation.stdout.on('data', function (data) { + return stderr.push(data.toString()); + }); compilation.stderr.on('data', function (data) { return stderr.push(data.toString()); }); compilation.on('close', function (code) { console.log('Finished compiling PureScript.'); + cache.compilationFinished = true; if (code !== 0) { - cache.compilation = cache.errors = stderr; + cache.errors = stderr.join(''); reject(true); } else { - cache.compilation = cache.warnings = stderr; + cache.warnings = stderr.join(''); resolve(psModule); } }); @@ -240,10 +244,10 @@ function rebuild(psModule) { return reject('psc-ide rebuild failed'); }); } - cache.errors = compileMessages; + cache.errors = compileMessages.join('\n'); reject('psc-ide rebuild failed'); } else { - cache.warnings = compileMessages; + cache.warnings = compileMessages.join('\n'); resolve(psModule); } }); @@ -348,7 +352,7 @@ function bundle(options, cache) { }); compilation.on('close', function (code) { if (code !== 0) { - cache.errors.concat(stderr); + cache.errors = (cache.errors || '') + stderr.join(''); return reject(true); } cache.bundle = stderr; @@ -467,6 +471,8 @@ function dargs(obj) { return args.push(arg, v); });else args.push(arg, obj[key]); - return args; + return args.filter(function (arg) { + return typeof arg !== 'boolean'; + }); }, []); } -- cgit v1.2.3 From d34bc169e84d52b28279786ff400daced5fd8b8a Mon Sep 17 00:00:00 2001 From: Alex Mingoia Date: Sat, 21 May 2016 16:53:29 -0700 Subject: Do not check-in transpiled source. --- index.js | 478 --------------------------------------------------------------- 1 file changed, 478 deletions(-) delete mode 100644 index.js diff --git a/index.js b/index.js deleted file mode 100644 index 5f41361..0000000 --- a/index.js +++ /dev/null @@ -1,478 +0,0 @@ -'use strict'; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; - -var colors = require('chalk'); -var debug = require('debug')('purs-loader'); -var loaderUtils = require('loader-utils'); -var globby = require('globby'); -var Promise = require('bluebird'); -var fs = Promise.promisifyAll(require('fs')); -var spawn = require('child_process').spawn; -var path = require('path'); -var retryPromise = require('promise-retry'); - -var psModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i; -var requireRegex = /require\(['"]\.\.\/([\w\.]+)['"]\)/g; - -module.exports = function purescriptLoader(source, map) { - var callback = this.async(); - var config = this.options; - var query = loaderUtils.parseQuery(this.query); - var webpackOptions = this.options.purescriptLoader || {}; - - var options = Object.assign({ - context: config.context, - psc: 'psc', - pscArgs: {}, - pscBundle: 'psc-bundle', - pscBundleArgs: {}, - pscIde: false, - pscIdeColors: webpackOptions.psc === 'psa' || query.psc === 'psa', - pscIdeArgs: {}, - bundleOutput: 'output/bundle.js', - bundleNamespace: 'PS', - bundle: false, - warnings: true, - output: 'output', - src: [path.join('src', '**', '*.purs'), path.join('bower_components', 'purescript-*', 'src', '**', '*.purs')], - ffi: [path.join('src', '**', '*.js'), path.join('bower_components', 'purescript-*', 'src', '**', '*.js')] - }, webpackOptions, query); - - this.cacheable && this.cacheable(); - - var cache = config.purescriptLoaderCache = config.purescriptLoaderCache || { - rebuild: false, - deferred: [], - bundleModules: [] - }; - - if (!config.purescriptLoaderInstalled) { - config.purescriptLoaderInstalled = true; - - // invalidate loader cache when bundle is marked as invalid (in watch mode) - this._compiler.plugin('invalid', function () { - cache = config.purescriptLoaderCache = { - rebuild: options.pscIde, - deferred: [], - ideServer: cache.ideServer - }; - }); - - // add psc warnings to webpack compilation warnings - this._compiler.plugin('after-compile', function (compilation, callback) { - if (options.warnings && cache.warnings) { - compilation.warnings.unshift('PureScript compilation:\n' + cache.warnings); - } - - if (cache.errors) { - compilation.errors.unshift('PureScript compilation:\n' + cache.errors); - } - - callback(); - }); - } - - var psModuleName = match(psModuleRegex, source); - var psModule = { - name: psModuleName, - load: function load(js) { - return callback(null, js); - }, - reject: function reject(error) { - return callback(error); - }, - srcPath: this.resourcePath, - srcDir: path.dirname(this.resourcePath), - jsPath: path.resolve(path.join(options.output, psModuleName, 'index.js')), - options: options, - cache: cache - }; - - debug('loader called', psModule.name); - - if (options.bundle) { - cache.bundleModules.push(psModule.name); - } - - if (cache.rebuild) { - return connectIdeServer(psModule).then(rebuild).then(toJavaScript).then(psModule.load).catch(psModule.reject); - } - - if (cache.compilationFinished) { - return toJavaScript(psModule).then(psModule.load).catch(psModule.reject); - } - - // We need to wait for compilation to finish before the loaders run so that - // references to compiled output are valid. - cache.deferred.push(psModule); - - if (!cache.compilationStarted) { - return compile(psModule).then(function () { - return Promise.map(cache.deferred, function (psModule) { - if (_typeof(cache.ideServer) === 'object') cache.ideServer.kill(); - return toJavaScript(psModule).then(psModule.load); - }); - }).catch(function (error) { - cache.deferred[0].reject(error); - cache.deferred.slice(1).forEach(function (psModule) { - return psModule.reject(true); - }); - }); - } -}; - -// The actual loader is executed *after* purescript compilation. -function toJavaScript(psModule) { - var options = psModule.options; - var cache = psModule.cache; - var bundlePath = path.resolve(options.bundleOutput); - var jsPath = cache.bundle ? bundlePath : psModule.jsPath; - - debug('loading JavaScript for', psModule.name); - - return Promise.props({ - js: fs.readFileAsync(jsPath, 'utf8'), - psModuleMap: psModuleMap(options.src, cache) - }).then(function (result) { - var js = ''; - - if (options.bundle) { - // if bundling, return a reference to the bundle - js = 'module.exports = require("' + path.relative(psModule.srcDir, options.bundleOutput) + '")["' + psModule.name + '"]'; - } else { - // replace require paths to output files generated by psc with paths - // to purescript sources, which are then also run through this loader. - var foreignRequire = 'require("' + path.resolve(path.join(psModule.options.output, psModule.name, 'foreign.js')) + '")'; - - js = result.js.replace(requireRegex, function (m, p1) { - return 'require("' + result.psModuleMap[p1] + '")'; - }).replace(/require\(['"]\.\/foreign['"]\)/g, foreignRequire); - } - - return js; - }); -} - -function compile(psModule) { - var options = psModule.options; - var cache = psModule.cache; - var stderr = []; - - if (cache.compilationStarted) return Promise.resolve(psModule); - - cache.compilationStarted = true; - - var args = dargs(Object.assign({ - _: options.src, - ffi: options.ffi, - output: options.output - }, options.pscArgs)); - - debug('spawning compiler %s %o', options.psc, args); - - return new Promise(function (resolve, reject) { - console.log('\nCompiling PureScript...'); - - var compilation = spawn(options.psc, args); - - compilation.stdout.on('data', function (data) { - return stderr.push(data.toString()); - }); - compilation.stderr.on('data', function (data) { - return stderr.push(data.toString()); - }); - - compilation.on('close', function (code) { - console.log('Finished compiling PureScript.'); - cache.compilationFinished = true; - if (code !== 0) { - cache.errors = stderr.join(''); - reject(true); - } else { - cache.warnings = stderr.join(''); - resolve(psModule); - } - }); - }).then(function (compilerOutput) { - if (options.bundle) { - return bundle(options, cache).then(function () { - return psModule; - }); - } - return psModule; - }); -} - -function rebuild(psModule) { - var options = psModule.options; - var cache = psModule.cache; - - debug('attempting rebuild with psc-ide-client %s', psModule.srcPath); - - var request = function request(body) { - return new Promise(function (resolve, reject) { - var args = dargs(options.pscIdeArgs); - var ideClient = spawn('psc-ide-client', args); - - ideClient.stdout.once('data', function (data) { - var res = null; - - try { - res = JSON.parse(data.toString()); - debug(res); - } catch (err) { - return reject(err); - } - - if (res && !Array.isArray(res.result)) { - return res.resultType === 'success' ? resolve(psModule) : reject('psc-ide rebuild failed'); - } - - Promise.map(res.result, function (item, i) { - debug(item); - return formatIdeResult(item, options, i, res.result.length); - }).then(function (compileMessages) { - if (res.resultType === 'error') { - if (res.result.some(function (item) { - return item.errorCode === 'UnknownModule'; - })) { - console.log('Unknown module, attempting full recompile'); - return compile(psModule).then(function () { - return request({ command: 'load' }); - }).then(resolve).catch(function () { - return reject('psc-ide rebuild failed'); - }); - } - cache.errors = compileMessages.join('\n'); - reject('psc-ide rebuild failed'); - } else { - cache.warnings = compileMessages.join('\n'); - resolve(psModule); - } - }); - }); - - ideClient.stderr.once('data', function (data) { - return reject(data.toString()); - }); - - ideClient.stdin.write(JSON.stringify(body)); - ideClient.stdin.write('\n'); - }); - }; - - return request({ - command: 'rebuild', - params: { - file: psModule.srcPath - } - }); -} - -function formatIdeResult(result, options, index, length) { - var srcPath = path.relative(options.context, result.filename); - var pos = result.position; - var fileAndPos = srcPath + ':' + pos.startLine + ':' + pos.startColumn; - var numAndErr = '[' + (index + 1) + '/' + length + ' ' + result.errorCode + ']'; - numAndErr = options.pscIdeColors ? colors.yellow(numAndErr) : numAndErr; - - return fs.readFileAsync(result.filename, 'utf8').then(function (source) { - var lines = source.split('\n').slice(pos.startLine - 1, pos.endLine); - var endsOnNewline = pos.endColumn === 1 && pos.startLine !== pos.endLine; - var up = options.pscIdeColors ? colors.red('^') : '^'; - var down = options.pscIdeColors ? colors.red('v') : 'v'; - var trimmed = lines.slice(0); - - if (endsOnNewline) { - lines.splice(lines.length - 1, 1); - pos.endLine = pos.endLine - 1; - pos.endColumn = lines[lines.length - 1].length || 1; - } - - // strip newlines at the end - if (endsOnNewline) { - trimmed = lines.reverse().reduce(function (trimmed, line, i) { - if (i === 0 && line === '') trimmed.trimming = true; - if (!trimmed.trimming) trimmed.push(line); - if (trimmed.trimming && line !== '') { - trimmed.trimming = false; - trimmed.push(line); - } - return trimmed; - }, []).reverse(); - pos.endLine = pos.endLine - (lines.length - trimmed.length); - pos.endColumn = trimmed[trimmed.length - 1].length || 1; - } - - var spaces = ' '.repeat(String(pos.endLine).length); - var snippet = trimmed.map(function (line, i) { - return ' ' + (pos.startLine + i) + ' ' + line; - }).join('\n'); - - if (trimmed.length === 1) { - snippet += '\n ' + spaces + ' ' + ' '.repeat(pos.startColumn - 1) + up.repeat(pos.endColumn - pos.startColumn + 1); - } else { - snippet = ' ' + spaces + ' ' + ' '.repeat(pos.startColumn - 1) + down + '\n' + snippet; - snippet += '\n ' + spaces + ' ' + ' '.repeat(pos.endColumn - 1) + up; - } - - return Promise.resolve('\n' + numAndErr + ' ' + fileAndPos + '\n\n' + snippet + '\n\n' + result.message); - }); -} - -function bundle(options, cache) { - if (cache.bundle) return Promise.resolve(cache.bundle); - - var stdout = []; - var stderr = cache.bundle = []; - - var args = dargs(Object.assign({ - _: [path.join(options.output, '*', '*.js')], - output: options.bundleOutput, - namespace: options.bundleNamespace - }, options.pscBundleArgs)); - - cache.bundleModules.forEach(function (name) { - return args.push('--module', name); - }); - - debug('spawning bundler %s %o', options.pscBundle, args.join(' ')); - - return new Promise(function (resolve, reject) { - console.log('Bundling PureScript...'); - - var compilation = spawn(options.pscBundle, args); - - compilation.stdout.on('data', function (data) { - return stdout.push(data.toString()); - }); - compilation.stderr.on('data', function (data) { - return stderr.push(data.toString()); - }); - compilation.on('close', function (code) { - if (code !== 0) { - cache.errors = (cache.errors || '') + stderr.join(''); - return reject(true); - } - cache.bundle = stderr; - resolve(fs.appendFileAsync('output/bundle.js', 'module.exports = ' + options.bundleNamespace)); - }); - }); -} - -// map of PS module names to their source path -function psModuleMap(globs, cache) { - if (cache.psModuleMap) return Promise.resolve(cache.psModuleMap); - - return globby(globs).then(function (paths) { - return Promise.props(paths.reduce(function (map, file) { - map[file] = fs.readFileAsync(file, 'utf8'); - return map; - }, {})).then(function (srcMap) { - cache.psModuleMap = Object.keys(srcMap).reduce(function (map, file) { - var source = srcMap[file]; - var psModuleName = match(psModuleRegex, source); - map[psModuleName] = path.resolve(file); - return map; - }, {}); - return cache.psModuleMap; - }); - }); -} - -function connectIdeServer(psModule) { - var options = psModule.options; - var cache = psModule.cache; - - if (cache.ideServer) return Promise.resolve(psModule); - - cache.ideServer = true; - - var connect = function connect() { - return new Promise(function (resolve, reject) { - var args = dargs(options.pscIdeArgs); - - debug('attempting to connect to psc-ide-server', args); - - var ideClient = spawn('psc-ide-client', args); - - ideClient.stderr.on('data', function (data) { - debug(data.toString()); - cache.ideServer = false; - reject(true); - }); - ideClient.stdout.once('data', function (data) { - debug(data.toString()); - if (data.toString()[0] === '{') { - var res = JSON.parse(data.toString()); - if (res.resultType === 'success') { - cache.ideServer = ideServer; - resolve(psModule); - } else { - cache.ideServer = ideServer; - reject(true); - } - } else { - cache.ideServer = false; - reject(true); - } - }); - ideClient.stdin.resume(); - ideClient.stdin.write(JSON.stringify({ command: 'load' })); - ideClient.stdin.write('\n'); - }); - }; - - var args = dargs(Object.assign({ - outputDirectory: options.output - }, options.pscIdeArgs)); - - debug('attempting to start psc-ide-server', args); - - var ideServer = cache.ideServer = spawn('psc-ide-server', []); - ideServer.stderr.on('data', function (data) { - debug(data.toString()); - }); - - return retryPromise(function (retry, number) { - return connect().catch(function (error) { - if (!cache.ideServer && number === 9) { - debug(error); - - console.log('failed to connect to or start psc-ide-server, ' + 'full compilation will occur on rebuild'); - - return Promise.resolve(psModule); - } - - return retry(error); - }); - }, { - retries: 9, - factor: 1, - minTimeout: 333, - maxTimeout: 333 - }); -} - -function match(regex, str) { - var matches = str.match(regex); - return matches && matches[1]; -} - -function dargs(obj) { - return Object.keys(obj).reduce(function (args, key) { - var arg = '--' + key.replace(/[A-Z]/g, '-$&').toLowerCase(); - var val = obj[key]; - - if (key === '_') val.forEach(function (v) { - return args.push(v); - });else if (Array.isArray(val)) val.forEach(function (v) { - return args.push(arg, v); - });else args.push(arg, obj[key]); - - return args.filter(function (arg) { - return typeof arg !== 'boolean'; - }); - }, []); -} -- cgit v1.2.3 From c0f935ab2372125cf00cdd722f83afa31e07eb9a Mon Sep 17 00:00:00 2001 From: Alex Mingoia Date: Sat, 21 May 2016 17:01:32 -0700 Subject: Add prepublish transpilation script. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 4cbdd72..d3d807f 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ ], "scripts": { "build": "babel src/index.js -o index.js", + "prepublish": "npm run build", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { -- cgit v1.2.3 From 17acb575860cf1bed9e1f6d992a9b7cd66057464 Mon Sep 17 00:00:00 2001 From: Alex Mingoia Date: Sat, 21 May 2016 17:04:26 -0700 Subject: Reference source ffi module instead of output ffi module. --- src/index.js | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/index.js b/src/index.js index e09d247..72da4d0 100644 --- a/src/index.js +++ b/src/index.js @@ -10,7 +10,8 @@ const spawn = require('child_process').spawn const path = require('path') const retryPromise = require('promise-retry') -const psModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i +const ffiModuleRegex = /\/\/\s+module\s+([\w\.]+)/i +const srcModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i const requireRegex = /require\(['"]\.\.\/([\w\.]+)['"]\)/g module.exports = function purescriptLoader(source, map) { @@ -77,7 +78,7 @@ module.exports = function purescriptLoader(source, map) { }) } - const psModuleName = match(psModuleRegex, source) + const psModuleName = match(srcModuleRegex, source) const psModule = { name: psModuleName, load: js => callback(null, js), @@ -135,7 +136,7 @@ function toJavaScript(psModule) { return Promise.props({ js: fs.readFileAsync(jsPath, 'utf8'), - psModuleMap: psModuleMap(options.src, cache) + psModuleMap: psModuleMap(options, cache) }).then(result => { let js = '' @@ -147,15 +148,13 @@ function toJavaScript(psModule) { } else { // replace require paths to output files generated by psc with paths // to purescript sources, which are then also run through this loader. - const foreignRequire = 'require("' + path.resolve( - path.join(psModule.options.output, psModule.name, 'foreign.js') - ) + '")' - js = result.js .replace(requireRegex, (m, p1) => { - return 'require("' + result.psModuleMap[p1] + '")' + return 'require("' + result.psModuleMap[p1].src + '")' + }) + .replace(/require\(['"]\.\/foreign['"]\)/g, (m, p1) => { + return 'require("' + result.psModuleMap[psModule.name].ffi + '")' }) - .replace(/require\(['"]\.\/foreign['"]\)/g, foreignRequire) } return js @@ -357,20 +356,30 @@ function bundle(options, cache) { } // map of PS module names to their source path -function psModuleMap(globs, cache) { +function psModuleMap(options, cache) { if (cache.psModuleMap) return Promise.resolve(cache.psModuleMap) + const globs = [].concat(options.src).concat(options.ffi) + return globby(globs).then(paths => { return Promise .props(paths.reduce((map, file) => { map[file] = fs.readFileAsync(file, 'utf8') return map }, {})) - .then(srcMap => { - cache.psModuleMap = Object.keys(srcMap).reduce((map, file) => { - const source = srcMap[file] - const psModuleName = match(psModuleRegex, source) - map[psModuleName] = path.resolve(file) + .then(fileMap => { + cache.psModuleMap = Object.keys(fileMap).reduce((map, file) => { + const source = fileMap[file] + const ext = path.extname(file) + const isPurs = ext.match(/purs$/i) + const moduleRegex = isPurs ? srcModuleRegex : ffiModuleRegex + const moduleName = match(moduleRegex, source) + map[moduleName] = map[moduleName] || {} + if (isPurs) { + map[moduleName].src = path.resolve(file) + } else { + map[moduleName].ffi = path.resolve(file) + } return map }, {}) return cache.psModuleMap -- cgit v1.2.3