From 9d38968bbe4bbf54e2ca836d9a1550d74d4da703 Mon Sep 17 00:00:00 2001 From: eric thul Date: Thu, 2 Apr 2015 18:18:36 -0400 Subject: Adding purescript imports as dependencies The purescript imports are added as webpack dependencies in order to rebuild in watch mode when one of the files has changed. Resolves #4 regarding watching purescript files. --- index.js | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 137 insertions(+), 35 deletions(-) diff --git a/index.js b/index.js index 4a74d15..449c841 100644 --- a/index.js +++ b/index.js @@ -1,62 +1,164 @@ -var cp = require('child_process') - , path = require('path') - , fs = require('fs') - , glob = require('glob') - , lodash = require('lodash') - , chalk = require('chalk') - , lu = require('loader-utils') - , cwd = process.cwd() - , MODULE_RE = /^module\s+([\w\.]+)\s+/i - , BOWER_PATTERN = path.join('bower_components', 'purescript-*', 'src') - , PSC_MAKE = 'psc-make' - , OUTPUT = 'output' - , OPTIONS = { - 'no-prelude': '--no-prelude', - 'no-opts': '--no-opts', - 'no-magic-do': '--no-magic-do', - 'no-tco': '--no-tco', - 'verbose-errors': '--verbose-errors', - 'output': '--output' - } -; +'use strict'; + +var os = require('os'); + +var cp = require('child_process'); + +var path = require('path') + +var fs = require('fs'); + +var glob = require('glob'); + +var lodash = require('lodash'); + +var chalk = require('chalk'); + +var lu = require('loader-utils'); + +var cwd = process.cwd(); + +var MODULE_RE = /(?:^|\n)module\s+([\w\.]+)/i; + +var IMPORT_RE = /^\s*import\s+(?:qualified\s+)?([\w\.]+)/i; + +var BOWER_PATTERN = path.join('bower_components', 'purescript-*', 'src'); + +var PSC_MAKE = 'psc-make'; + +var OUTPUT = 'output'; + +var OPTIONS = { + 'no-prelude': '--no-prelude', + 'no-opts': '--no-opts', + 'no-magic-do': '--no-magic-do', + 'no-tco': '--no-tco', + 'verbose-errors': '--verbose-errors', + 'output': '--output' +}; function pattern(root) { var as = [ BOWER_PATTERN, root ]; return path.join('{' + as.join(',') + '}', '**', '*.purs'); } -module.exports = function(source){ - var callback = this.async() - , request = lu.getRemainingRequest(this) - , root = path.dirname(path.relative(cwd, request)) - , query = lu.parseQuery(this.query) - , opts = lodash.foldl(lodash.keys(query), function(acc, k){ - var h = function(v){return acc.concat(query[k] && OPTIONS[k] ? [v] : []);} - if (k === OUTPUT) return h(OPTIONS[k] + '=' + query[k]); - else return h(OPTIONS[k]); +function mkOptions(query) { + return lodash.foldl(lodash.keys(query), function(acc, k){ + var h = function(v){return acc.concat(query[k] && OPTIONS[k] ? [v] : []);} + if (k === OUTPUT) return h(OPTIONS[k] + '=' + query[k]); + else return h(OPTIONS[k]); + }, []); +} + +function mkGraph(files) { + var graph = {}; + + files.forEach(function(file){ + var source = fs.readFileSync(file, {encoding: 'utf-8'}); + + var result = MODULE_RE.exec(source); + + var module = result ? result[1] : null; + + var imports = + lodash.foldl(source.split(os.EOL), function(b, a){ + var result = IMPORT_RE.exec(a); + if (result) b.push(result[1]); + return b; }, []) - ; + ; + + if (module) { + graph[module] = { + file: file, + imports: imports || [] + }; + } + }); + + return graph; +} + +function findDeps(graph, module) { + function go(acc, module){ + var node = graph[module]; + + var imports = node && node.imports; + + if (lodash.isEmpty(imports)) return acc; + else { + var deps = + lodash.map(imports, function(i){ + return go(acc.concat(imports), i); + }) + ; + return lodash.flatten(deps); + } + } + + return lodash.unique(go([], module)); +} + +function loader(source) { + this.cacheable && this.cacheable(); + + this.clearDependencies(); + + this.addDependency(this.resourcePath); + + var callback = this.async(); + + var request = lu.getRemainingRequest(this) + + var root = path.dirname(path.relative(cwd, request)); + + var query = lu.parseQuery(this.query); + + var opts = mkOptions(query); + + var that = this; + glob(pattern(root), function(e, files){ if (e !== null) callback(e); else { var cmd = cp.spawn(PSC_MAKE, opts.concat(files)); + + var graph = mkGraph(files); + cmd.on('close', function(e){ if (e) callback(e); else { var result = MODULE_RE.exec(source); - var module = result.length > 1 ? result[1] : ''; - fs.readFile(path.join(query[OUTPUT] || OUTPUT, module, 'index.js'), 'utf-8', function(e, output){ + + var module = result ? result[1] : ''; + + var dependencies = findDeps(graph, module); + + var indexPath = path.join(query[OUTPUT] || OUTPUT, module, 'index.js'); + + fs.readFile(indexPath, 'utf-8', function(e, output){ if (e) callback(e); - else callback(e, output); + else { + dependencies.forEach(function(dep){ + var module = graph[dep]; + if (module) that.addDependency(path.resolve(module.file)); + }); + + callback(null, output); + } }); } }); + cmd.stdout.on('data', function(stdout){ console.log('Stdout from \'' + chalk.cyan(PSC_MAKE) + '\'\n' + chalk.magenta(stdout)); }); + cmd.stderr.on('data', function(stderr){ console.log('Stderr from \'' + chalk.cyan(PSC_MAKE) + '\'\n' + chalk.magenta(stderr)); }); } }); -}; +} + +module.exports = loader; -- cgit v1.2.3 From c194f84cab66fa6e18b78c32f9cdf2bddf8d1e68 Mon Sep 17 00:00:00 2001 From: eric thul Date: Wed, 8 Apr 2015 19:49:24 -0400 Subject: Rewrite using purescript for the implementation --- .gitignore | 5 ++ MODULE.md | 226 ++++++++++++++++++++++++++++++++++++++++++++++++++ bower.json | 16 ++++ entry.js | 11 +++ gulpfile.js | 68 +++++++++++++++ index.js | 164 ------------------------------------ package.json | 38 +++++---- src/ChildProcess.purs | 40 +++++++++ src/FS.purs | 45 ++++++++++ src/Glob.purs | 31 +++++++ src/Loader.purs | 114 +++++++++++++++++++++++++ src/LoaderRef.purs | 75 +++++++++++++++++ src/LoaderUtil.purs | 20 +++++ src/OS.purs | 3 + src/Options.purs | 72 ++++++++++++++++ src/Path.purs | 36 ++++++++ webpack.config.js | 29 +++++++ 17 files changed, 814 insertions(+), 179 deletions(-) create mode 100644 MODULE.md create mode 100644 bower.json create mode 100644 entry.js create mode 100644 gulpfile.js delete mode 100644 index.js create mode 100644 src/ChildProcess.purs create mode 100644 src/FS.purs create mode 100644 src/Glob.purs create mode 100644 src/Loader.purs create mode 100644 src/LoaderRef.purs create mode 100644 src/LoaderUtil.purs create mode 100644 src/OS.purs create mode 100644 src/Options.purs create mode 100644 src/Path.purs create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore index 8cde684..e58d33a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ +.psci npm-debug.log +index.json +index.js node_modules/ +bower_components/ +build/ example/node_modules/ example/bower_components/ example/dist/ diff --git a/MODULE.md b/MODULE.md new file mode 100644 index 0000000..39d9d3a --- /dev/null +++ b/MODULE.md @@ -0,0 +1,226 @@ +# Module Documentation + +## Module PursLoader.ChildProcess + +#### `ChildProcess` + +``` purescript +data ChildProcess :: ! +``` + + +#### `spawn` + +``` purescript +spawn :: forall eff. String -> [String] -> Aff (cp :: ChildProcess | eff) String +``` + + + +## Module PursLoader.FS + +#### `FS` + +``` purescript +data FS :: ! +``` + + +#### `readFileUtf8` + +``` purescript +readFileUtf8 :: forall eff. String -> Aff (fs :: FS | eff) String +``` + + +#### `readFileUtf8Sync` + +``` purescript +readFileUtf8Sync :: forall eff. String -> Eff (fs :: FS | eff) String +``` + + + +## Module PursLoader.Glob + +#### `Glob` + +``` purescript +data Glob :: ! +``` + + +#### `glob` + +``` purescript +glob :: forall eff. String -> Aff (glob :: Glob | eff) [String] +``` + + + +## Module PursLoader.Loader + +#### `LoaderEff` + +``` purescript +type LoaderEff eff a = Eff (fs :: FS, cp :: ChildProcess, glob :: Glob, loader :: Loader | eff) a +``` + + +#### `loader` + +``` purescript +loader :: forall eff. LoaderRef -> String -> LoaderEff eff Unit +``` + + +#### `loaderFn` + +``` purescript +loaderFn :: forall eff. Fn2 LoaderRef String (LoaderEff eff Unit) +``` + + + +## Module PursLoader.LoaderRef + +#### `LoaderRef` + +``` purescript +data LoaderRef +``` + + +#### `Loader` + +``` purescript +data Loader :: ! +``` + + +#### `async` + +``` purescript +async :: forall eff a. LoaderRef -> Eff (loader :: Loader | eff) (Maybe Error -> a -> 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 +``` + + +#### `query` + +``` purescript +query :: LoaderRef -> String +``` + + + +## Module PursLoader.LoaderUtil + +#### `getRemainingRequest` + +``` purescript +getRemainingRequest :: LoaderRef -> String +``` + + +#### `parseQuery` + +``` purescript +parseQuery :: String -> Foreign +``` + + + +## Module PursLoader.OS + +#### `eol` + +``` purescript +eol :: String +``` + + + +## Module PursLoader.Options + +#### `isForeignOptions` + +``` purescript +instance isForeignOptions :: IsForeign Options +``` + + +#### `pscMakeOutputOption` + +``` purescript +pscMakeOutputOption :: Foreign -> Maybe String +``` + + +#### `pscMakeOptions` + +``` purescript +pscMakeOptions :: Foreign -> [String] +``` + + + +## Module PursLoader.Path + +#### `dirname` + +``` purescript +dirname :: String -> String +``` + + +#### `join` + +``` purescript +join :: [String] -> String +``` + + +#### `relative` + +``` purescript +relative :: String -> String -> String +``` + + +#### `resolve` + +``` purescript +resolve :: String -> String +``` + + + + diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..dddddf9 --- /dev/null +++ b/bower.json @@ -0,0 +1,16 @@ +{ + "name": "purs-loader", + "private": true, + "devDependencies": { + "purescript-aff": "~0.9.1", + "purescript-exceptions": "~0.2.3", + "purescript-strings": "~0.4.5", + "purescript-maybe": "~0.2.2", + "purescript-foreign": "~0.4.2", + "purescript-foldable-traversable": "~0.3.1", + "purescript-tuples": "~0.3.4", + "purescript-maps": "~0.3.3", + "purescript-arrays": "~0.3.7", + "purescript-monad-eff": "~0.1.0" + } +} diff --git a/entry.js b/entry.js new file mode 100644 index 0000000..87f52d3 --- /dev/null +++ b/entry.js @@ -0,0 +1,11 @@ +'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/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..e217480 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,68 @@ +'use strict'; + +var path = require('path'); + +var gulp = require('gulp'); + +var gutil = require('gulp-util'); + +var plumber = require('gulp-plumber'); + +var purescript = require('gulp-purescript'); + +var sequence = require('run-sequence'); + +var del = require('del'); + +var config = { del: ['build', 'index.js'] + , purescript: { src: [ 'bower_components/purescript-*/src/**/*.purs*' + , 'src/**/*.purs' + ] + , dest: 'build' + , docs: 'MODULE.md' + } + } + ; + +function error(e) { + gutil.log(gutil.colors.magenta('>>>> Error <<<<') + '\n' + e.toString().trim()); + this.emit('end'); +} + +gulp.task('del', function(cb){ + del(config.del, cb); +}); + +gulp.task('make', function(){ + return gulp.src(config.purescript.src). + pipe(plumber()). + pipe(purescript.pscMake({output: config.purescript.dest})). + on('error', error); +}); + +gulp.task('psci', function(){ + return gulp.src(config.purescript.src). + pipe(plumber()). + pipe(purescript.dotPsci()). + on('error', error); +}); + +gulp.task('docs', function(){ + return gulp.src(config.purescript.src[1]). + pipe(plumber()). + pipe(purescript.pscDocs()). + on('error', error). + pipe(gulp.dest(config.purescript.docs)); +}); + +gulp.task('watch', function(){ + gulp.watch(config.purescript.src, ['make']); +}); + +gulp.task('default', function(callback){ + sequence('del', 'make', ['psci', 'docs'], callback); +}); + +gulp.task('build', function(callback){ + sequence('del', 'make', callback); +}); diff --git a/index.js b/index.js deleted file mode 100644 index 449c841..0000000 --- a/index.js +++ /dev/null @@ -1,164 +0,0 @@ -'use strict'; - -var os = require('os'); - -var cp = require('child_process'); - -var path = require('path') - -var fs = require('fs'); - -var glob = require('glob'); - -var lodash = require('lodash'); - -var chalk = require('chalk'); - -var lu = require('loader-utils'); - -var cwd = process.cwd(); - -var MODULE_RE = /(?:^|\n)module\s+([\w\.]+)/i; - -var IMPORT_RE = /^\s*import\s+(?:qualified\s+)?([\w\.]+)/i; - -var BOWER_PATTERN = path.join('bower_components', 'purescript-*', 'src'); - -var PSC_MAKE = 'psc-make'; - -var OUTPUT = 'output'; - -var OPTIONS = { - 'no-prelude': '--no-prelude', - 'no-opts': '--no-opts', - 'no-magic-do': '--no-magic-do', - 'no-tco': '--no-tco', - 'verbose-errors': '--verbose-errors', - 'output': '--output' -}; - -function pattern(root) { - var as = [ BOWER_PATTERN, root ]; - return path.join('{' + as.join(',') + '}', '**', '*.purs'); -} - -function mkOptions(query) { - return lodash.foldl(lodash.keys(query), function(acc, k){ - var h = function(v){return acc.concat(query[k] && OPTIONS[k] ? [v] : []);} - if (k === OUTPUT) return h(OPTIONS[k] + '=' + query[k]); - else return h(OPTIONS[k]); - }, []); -} - -function mkGraph(files) { - var graph = {}; - - files.forEach(function(file){ - var source = fs.readFileSync(file, {encoding: 'utf-8'}); - - var result = MODULE_RE.exec(source); - - var module = result ? result[1] : null; - - var imports = - lodash.foldl(source.split(os.EOL), function(b, a){ - var result = IMPORT_RE.exec(a); - if (result) b.push(result[1]); - return b; - }, []) - ; - - if (module) { - graph[module] = { - file: file, - imports: imports || [] - }; - } - }); - - return graph; -} - -function findDeps(graph, module) { - function go(acc, module){ - var node = graph[module]; - - var imports = node && node.imports; - - if (lodash.isEmpty(imports)) return acc; - else { - var deps = - lodash.map(imports, function(i){ - return go(acc.concat(imports), i); - }) - ; - return lodash.flatten(deps); - } - } - - return lodash.unique(go([], module)); -} - -function loader(source) { - this.cacheable && this.cacheable(); - - this.clearDependencies(); - - this.addDependency(this.resourcePath); - - var callback = this.async(); - - var request = lu.getRemainingRequest(this) - - var root = path.dirname(path.relative(cwd, request)); - - var query = lu.parseQuery(this.query); - - var opts = mkOptions(query); - - var that = this; - - glob(pattern(root), function(e, files){ - if (e !== null) callback(e); - else { - var cmd = cp.spawn(PSC_MAKE, opts.concat(files)); - - var graph = mkGraph(files); - - cmd.on('close', function(e){ - if (e) callback(e); - else { - var result = MODULE_RE.exec(source); - - var module = result ? result[1] : ''; - - var dependencies = findDeps(graph, module); - - var indexPath = path.join(query[OUTPUT] || OUTPUT, module, 'index.js'); - - fs.readFile(indexPath, 'utf-8', function(e, output){ - if (e) callback(e); - else { - dependencies.forEach(function(dep){ - var module = graph[dep]; - if (module) that.addDependency(path.resolve(module.file)); - }); - - callback(null, output); - } - }); - } - }); - - cmd.stdout.on('data', function(stdout){ - console.log('Stdout from \'' + chalk.cyan(PSC_MAKE) + '\'\n' + chalk.magenta(stdout)); - }); - - cmd.stderr.on('data', function(stderr){ - console.log('Stderr from \'' + chalk.cyan(PSC_MAKE) + '\'\n' + chalk.magenta(stderr)); - }); - } - }); -} - -module.exports = loader; diff --git a/package.json b/package.json index 4d458b8..56c6b39 100644 --- a/package.json +++ b/package.json @@ -2,24 +2,32 @@ "name": "purs-loader", "version": "0.0.3", "description": "PureScript loader for webpack", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "license": "MIT", + "repository": "ethul/purs-loader", + "author": { + "name": "Eric Thul", + "email": "thul.eric@gmail.com" }, - "repository": { - "type": "git", - "url": "git@github.com:ethul/purs-loader.git" + "scripts": { + "build": "npm run-script build:compile && npm run-script build:package", + "build:compile": "gulp build", + "build:package": "./node_modules/.bin/webpack --progress --colors --profile --bail", + "build:json": "./node_modules/.bin/webpack --progress --colors --profile --bail --json > index.json" }, - "author": "Eric Thul", - "license": "MIT", - "bugs": { - "url": "https://github.com/ethul/purs-loader/issues" + "files": [ + "index.js" + ], + "devDependencies": { + "del": "^1.1.1", + "gulp": "^3.8.11", + "gulp-plumber": "^1.0.0", + "gulp-purescript": "^0.3.1", + "gulp-util": "^3.0.4", + "run-sequence": "^1.0.2", + "webpack": "^1.8.4" }, - "homepage": "https://github.com/ethul/purs-loader", "dependencies": { - "chalk": "^0.5.1", - "glob": "4.0.6", - "loader-utils": "^0.2.3", - "lodash": "^2.4.1" + "glob": "^5.0.3", + "loader-utils": "^0.2.6" } } diff --git a/src/ChildProcess.purs b/src/ChildProcess.purs new file mode 100644 index 0000000..c9ff23b --- /dev/null +++ b/src/ChildProcess.purs @@ -0,0 +1,40 @@ +module PursLoader.ChildProcess + ( ChildProcess() + , spawn + ) where + +import Control.Monad.Aff (Aff(), makeAff) +import Control.Monad.Eff (Eff()) +import Control.Monad.Eff.Exception (Error()) + +import Data.Function + +foreign import data ChildProcess :: ! + +spawn :: forall eff. String -> [String] -> Aff (cp :: ChildProcess | eff) String +spawn command args = makeAff $ runFn4 spawnFn command args + +foreign import spawnFn """ +function spawnFn(command, args, errback, callback) { + return function(){ + var child_process = require('child_process'); + + var process = child_process.spawn(command, args); + + var stdout = new Buffer(0); + + process.stdout.on('data', function(data){ + stdout = Buffer.concat([stdout, new Buffer(data)]); + }); + + process.on('close', function(code){ + if (code !== 0) errback(new Error(stdout.toString()))(); + else callback(stdout.toString())(); + }); + }; +} +""" :: forall eff. Fn4 String + [String] + (Error -> Eff (cp :: ChildProcess | eff) Unit) + (String -> Eff (cp :: ChildProcess | eff) Unit) + (Eff (cp :: ChildProcess | eff) Unit) diff --git a/src/FS.purs b/src/FS.purs new file mode 100644 index 0000000..68fe2f9 --- /dev/null +++ b/src/FS.purs @@ -0,0 +1,45 @@ +module PursLoader.FS + ( FS() + , readFileUtf8 + , readFileUtf8Sync + ) where + +import Control.Monad.Aff (Aff(), makeAff) +import Control.Monad.Eff (Eff()) +import Control.Monad.Eff.Exception (Error()) + +import Data.Function + +foreign import data FS :: ! + +readFileUtf8 :: forall eff. String -> Aff (fs :: FS | eff) String +readFileUtf8 filepath = makeAff $ runFn3 readFileUtf8Fn filepath + +readFileUtf8Sync :: forall eff. String -> Eff (fs :: FS | eff) String +readFileUtf8Sync filepath = readFileUtf8SyncFn filepath + +foreign import readFileUtf8Fn """ +function readFileUtf8Fn(filepath, errback, callback) { + return function(){ + var fs = require('fs'); + + fs.readFile(filepath, 'utf-8', function(e, data){ + if (e) errback(e)(); + else callback(data)(); + }); + }; +} +""" :: forall eff. Fn3 String + (Error -> Eff (fs :: FS | eff) Unit) + (String -> Eff (fs :: FS | eff) Unit) + (Eff (fs :: FS | eff) Unit) + +foreign import readFileUtf8SyncFn """ +function readFileUtf8SyncFn(filepath) { + return function(){ + var fs = require('fs'); + + return fs.readFileSync(filepath, {encoding: 'utf-8'}); + }; +} +""" :: forall eff. String -> (Eff (fs :: FS | eff) String) diff --git a/src/Glob.purs b/src/Glob.purs new file mode 100644 index 0000000..7bc9212 --- /dev/null +++ b/src/Glob.purs @@ -0,0 +1,31 @@ +module PursLoader.Glob + ( Glob() + , glob + ) where + +import Control.Monad.Aff (Aff(), makeAff) +import Control.Monad.Eff (Eff()) +import Control.Monad.Eff.Exception (Error()) + +import Data.Function + +foreign import data Glob :: ! + +glob :: forall eff. String -> Aff (glob :: Glob | eff) [String] +glob pattern = makeAff $ runFn3 globFn pattern + +foreign import globFn """ +function globFn(pattern, errback, callback) { + return function(){ + var glob = require('glob'); + + glob(pattern, function(e, data){ + if (e) errback(e)(); + else callback(data)(); + }); + }; +} +""" :: forall eff. Fn3 String + (Error -> Eff (glob :: Glob | eff) Unit) + ([String] -> Eff (glob :: Glob | eff) Unit) + (Eff (glob :: Glob | eff) Unit) diff --git a/src/Loader.purs b/src/Loader.purs new file mode 100644 index 0000000..523aa7a --- /dev/null +++ b/src/Loader.purs @@ -0,0 +1,114 @@ +module PursLoader.Loader + ( LoaderEff() + , loader + , loaderFn + ) where + +import Control.Monad.Aff (Aff(), runAff) +import Control.Monad.Eff (Eff()) +import Control.Monad.Eff.Class (liftEff) +import Control.Monad.Eff.Exception (error) + +import Data.Array ((!!), catMaybes, concat, nub, null) +import Data.Function (Fn2(), mkFn2) +import Data.Maybe (Maybe(..), fromMaybe, maybe) +import Data.String (joinWith, split) +import Data.String.Regex (Regex(), match, noFlags, regex) +import Data.StrMap (StrMap(), fromList, lookup) +import Data.Traversable (sequence) +import Data.Tuple.Nested (tuple2) + +import PursLoader.ChildProcess (ChildProcess(), spawn) +import PursLoader.FS (FS(), readFileUtf8, readFileUtf8Sync) +import PursLoader.Glob (Glob(), glob) +import PursLoader.LoaderRef (LoaderRef(), Loader(), async, cacheable, clearDependencies, addDependency, query, resourcePath) +import PursLoader.LoaderUtil (getRemainingRequest, parseQuery) +import PursLoader.OS (eol) +import PursLoader.Options (pscMakeOptions, pscMakeDefaultOutput, pscMakeOutputOption) +import PursLoader.Path (dirname, join, relative, resolve) + +foreign import cwd "var cwd = process.cwd();" :: String + +moduleRegex = regex "(?:^|\\n)module\\s+([\\w\\.]+)" noFlags { ignoreCase = true } + +importRegex = regex "^\\s*import\\s+(?:qualified\\s+)?([\\w\\.]+)" noFlags { ignoreCase = true } + +bowerPattern = join [ "bower_components", "purescript-*", "src" ] + +pscMakeCommand = "psc-make" + +indexFilename = "index.js" + +(!!!) = flip (!!) + +pursPattern :: String -> String +pursPattern root = join [ "{" ++ joinWith "," [ bowerPattern, root ] ++ "}" + , "**" + , "*.purs" + ] + +type GraphModule = { file :: String, imports :: [String] } + +type Graph = StrMap GraphModule + +mkGraph :: forall eff. [String] -> Eff (fs :: FS | eff) Graph +mkGraph files = (fromList <<< catMaybes) <$> sequence (parse <$> files) + where parse file = do source <- readFileUtf8Sync file + let key = match moduleRegex source >>= (!!!) 1 + lines = split eol source + imports = catMaybes $ (\a -> match importRegex a >>= (!!!) 1) <$> lines + return $ (\a -> tuple2 a { file: file, imports: imports }) <$> key + +mkDeps :: forall eff. String -> Graph -> [String] +mkDeps key graph = nub $ go [] key + where go acc key = + maybe acc (\a -> if null a.imports + then acc + else concat $ go (acc <> a.imports) <$> a.imports) (lookup key graph) + +addDeps :: forall eff. LoaderRef -> Graph -> [String] -> Eff (loader :: Loader | eff) Unit +addDeps ref graph deps = const unit <$> (sequence $ add <$> deps) + where add dep = let res = lookup dep graph + path = (\a -> resolve a.file) <$> res + in maybe (pure unit) (addDependency ref) path + +type LoaderAff eff a = Aff (loader :: Loader, glob :: Glob, cp :: ChildProcess, fs :: FS | eff) a + +loader' :: forall eff. LoaderRef -> String -> LoaderAff eff (Maybe String) +loader' ref source = do + liftEff $ cacheable ref + + let request = getRemainingRequest ref + root = dirname $ relative cwd request + parsed = parseQuery $ query ref + opts = pscMakeOptions parsed + pattern = pursPattern root + key = match moduleRegex source >>= (!!!) 1 + + files <- glob pattern + graph <- liftEff $ mkGraph files + + let deps = fromMaybe [] $ flip mkDeps graph <$> key + outputPath = fromMaybe pscMakeDefaultOutput $ pscMakeOutputOption parsed + indexPath = (\a -> join [ outputPath, a, indexFilename ]) <$> key + + liftEff $ clearDependencies ref + liftEff $ addDependency ref (resourcePath ref) + liftEff $ addDeps ref graph deps + + spawn pscMakeCommand (opts <> files) + indexFile <- sequence $ readFileUtf8 <$> indexPath + return indexFile + +type LoaderEff eff a = Eff (loader :: Loader, glob :: Glob, cp :: ChildProcess, fs :: FS | eff) a + +loader :: forall eff. LoaderRef -> String -> LoaderEff eff Unit +loader ref source = do + callback <- async ref + runAff (\e -> callback (Just e) "") + (maybe (callback (Just $ error "Loader has failed to run") "") + (callback Nothing)) + (loader' ref source) + +loaderFn :: forall eff. Fn2 LoaderRef String (LoaderEff eff Unit) +loaderFn = mkFn2 loader diff --git a/src/LoaderRef.purs b/src/LoaderRef.purs new file mode 100644 index 0000000..2d62754 --- /dev/null +++ b/src/LoaderRef.purs @@ -0,0 +1,75 @@ +module PursLoader.LoaderRef + ( LoaderRef() + , Loader() + , async + , cacheable + , clearDependencies + , resourcePath + , addDependency + , query + ) where + +import Control.Monad.Eff (Eff()) +import Control.Monad.Eff.Exception (Error()) + +import Data.Foreign (Foreign()) +import Data.Function (Fn3(), runFn3) +import Data.Maybe (Maybe(), fromMaybe, isJust) + +data LoaderRef + +foreign import data Loader :: ! + +foreign import asyncFn """ +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); + }; + }; + }; + }; +}""" :: forall eff a. Fn3 (Maybe Error -> Boolean) + (Error -> Maybe Error -> Error) + LoaderRef + (Eff (loader :: Loader | eff) (Maybe Error -> a -> Eff (loader :: Loader | eff) Unit)) + +async :: forall eff a. LoaderRef -> Eff (loader :: Loader | eff) (Maybe Error -> a -> Eff (loader :: Loader | eff) Unit) +async ref = runFn3 asyncFn isJust fromMaybe ref + +foreign import cacheable """ +function cacheable(ref){ + return function(){ + return ref.cacheable && ref.cacheable(); + }; +}""" :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit + +foreign import clearDependencies """ +function clearDependencies(ref){ + return function(){ + return ref.clearDependencies(); + }; +}""" :: forall eff. LoaderRef -> Eff (loader :: Loader | eff) Unit + +foreign import resourcePath """ +function resourcePath(ref){ + return ref.resourcePath; +}""" :: LoaderRef -> String + +foreign import addDependency """ +function addDependency(ref){ + return function(dep){ + return function(){ + return ref.addDependency(dep); + }; + }; +}""" :: forall eff. LoaderRef -> String -> Eff (loader :: Loader | eff) Unit + +foreign import query """ +function query(ref){ + return ref.query; +}""" :: LoaderRef -> String diff --git a/src/LoaderUtil.purs b/src/LoaderUtil.purs new file mode 100644 index 0000000..f22be44 --- /dev/null +++ b/src/LoaderUtil.purs @@ -0,0 +1,20 @@ +module PursLoader.LoaderUtil + ( getRemainingRequest + , parseQuery + ) where + +import Data.Foreign (Foreign()) + +import PursLoader.LoaderRef (LoaderRef()) + +foreign import getRemainingRequest """ +function getRemainingRequest(ref){ + var loaderUtils = require('loader-utils'); + return loaderUtils.getRemainingRequest(ref); +}""" :: LoaderRef -> String + +foreign import parseQuery """ +function parseQuery(query){ + var loaderUtils = require('loader-utils'); + return loaderUtils.parseQuery(query); +}""" :: String -> Foreign diff --git a/src/OS.purs b/src/OS.purs new file mode 100644 index 0000000..590c3d6 --- /dev/null +++ b/src/OS.purs @@ -0,0 +1,3 @@ +module PursLoader.OS (eol) where + +foreign import eol "var eol = require('os').EOL;" :: String diff --git a/src/Options.purs b/src/Options.purs new file mode 100644 index 0000000..b96cddc --- /dev/null +++ b/src/Options.purs @@ -0,0 +1,72 @@ +module PursLoader.Options + ( pscMakeOptions + , pscMakeDefaultOutput + , pscMakeOutputOption + ) where + +import Data.Either (either) + +import Data.Foreign (Foreign(), F()) +import Data.Foreign.Class (IsForeign, read, readProp) +import Data.Foreign.NullOrUndefined (NullOrUndefined(), runNullOrUndefined) + +import Data.Maybe (Maybe(..), maybe) + +noPreludeOpt = "no-prelude" + +noOptsOpt = "no-opts" + +noMagicDoOpt = "no-magic-do" + +noTcoOpt = "no-tco" + +verboseErrorsOpt = "verbose-errors" + +outputOpt = "output" + +pscMakeDefaultOutput = "output" + +newtype Options + = Options { noPrelude :: NullOrUndefined Boolean + , noOpts :: NullOrUndefined Boolean + , noMagicDo :: NullOrUndefined Boolean + , noTco :: NullOrUndefined Boolean + , verboseErrors :: NullOrUndefined Boolean + , output :: NullOrUndefined String + } + +instance isForeignOptions :: IsForeign Options where + read obj = (\a b c d e f -> + Options { noPrelude: a + , noOpts: b + , noMagicDo: c + , noTco: d + , verboseErrors: e + , output: f + }) <$> readProp noPreludeOpt obj + <*> readProp noOptsOpt obj + <*> readProp noMagicDoOpt obj + <*> readProp noTcoOpt obj + <*> readProp verboseErrorsOpt obj + <*> readProp outputOpt obj + +booleanOpt :: String -> NullOrUndefined Boolean -> [String] +booleanOpt key opt = maybe [] (\a -> if a then ["--" ++ key] else []) (runNullOrUndefined opt) + +stringOpt :: String -> NullOrUndefined String -> [String] +stringOpt key opt = maybe [] (\a -> ["--" ++ key ++ "=" ++ a]) (runNullOrUndefined opt) + +pscMakeOutputOption :: Foreign -> Maybe String +pscMakeOutputOption query = either (const Nothing) + (\(Options a) -> runNullOrUndefined a.output) + (read query) + +pscMakeOptions :: Foreign -> [String] +pscMakeOptions query = either (const []) fold parsed + where parsed = read query :: F Options + fold (Options a) = booleanOpt noPreludeOpt a.noPrelude <> + booleanOpt noOptsOpt a.noOpts <> + booleanOpt noMagicDoOpt a.noMagicDo <> + booleanOpt noTcoOpt a.noTco <> + booleanOpt verboseErrorsOpt a.verboseErrors <> + stringOpt outputOpt a.output diff --git a/src/Path.purs b/src/Path.purs new file mode 100644 index 0000000..e071e35 --- /dev/null +++ b/src/Path.purs @@ -0,0 +1,36 @@ +module PursLoader.Path + ( dirname + , join + , relative + , resolve + ) where + +foreign import dirname """ +function dirname(filepath) { + var path = require('path'); + return path.dirname(filepath); +} +""" :: String -> String + +foreign import join """ +function join(parts) { + var path = require('path'); + return path.join.apply(path, parts); +} +""" :: [String] -> String + +foreign import relative """ +function relative(from) { + return function(to){ + var path = require('path'); + return path.relative(from, to); + }; +} +""" :: String -> String -> String + +foreign import resolve """ +function resolve(filepath) { + var path = require('path'); + return path.resolve(filepath); +} +""" :: String -> String diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..11b9069 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,29 @@ +'use strict'; + +var path = require('path'); + +var webpack = require('webpack'); + +var noErrorsPlugin = webpack.NoErrorsPlugin; + +var dedupePlugin = webpack.optimize.DedupePlugin; + +var config + = { cache: true + , target: 'node' + , entry: { index: './entry' } + , externals: { 'glob': 'commonjs glob' + , 'loader-utils': 'commonjs loader-utils' + } + , output: { path: __dirname + , filename: '[name].js' + , libraryTarget: 'commonjs2' + } + , plugins: [ new noErrorsPlugin() + , new dedupePlugin() + ] + , resolve: { modulesDirectories: [ 'build' ] } + } + ; + +module.exports = config; -- cgit v1.2.3 From 464355c740940df632eef951c6c84eb2767b5ab3 Mon Sep 17 00:00:00 2001 From: eric thul Date: Sun, 12 Apr 2015 11:08:05 -0400 Subject: Updating example and adding publishing Resolves #7 --- .gitignore | 2 +- LICENSE | 2 +- README.md | 2 +- example/bower.json | 8 -------- example/package.json | 6 +++--- example/src/entry.js | 6 +++--- example/webpack.config.js | 30 ++++++++++++++++-------------- package.json | 3 ++- 8 files changed, 27 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index e58d33a..dd8eead 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ index.js node_modules/ bower_components/ build/ +example/bundle.js example/node_modules/ example/bower_components/ -example/dist/ example/output/ diff --git a/LICENSE b/LICENSE index aaed7de..05b0016 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014 Eric Thul +Copyright (c) 2015 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 dcbedf2..ee96448 100644 --- a/README.md +++ b/README.md @@ -27,4 +27,4 @@ npm install purs-loader --save-dev ## Example -See the [example](https://github.com/ethul/purs-loader/tree/topic/bower-components/example) directory for a complete example. +See the [example](https://github.com/ethul/purs-loader/tree/master/example) directory for a complete example. diff --git a/example/bower.json b/example/bower.json index 8d71688..b4a1c78 100644 --- a/example/bower.json +++ b/example/bower.json @@ -1,14 +1,6 @@ { "name": "example", - "license": "MIT", "private": true, - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], "devDependencies": { "purescript-maybe": "~0.2.1" } diff --git a/example/package.json b/example/package.json index 6b0c04c..915eb27 100644 --- a/example/package.json +++ b/example/package.json @@ -4,12 +4,12 @@ "private": true, "scripts": { "webpack": "./node_modules/.bin/webpack", - "run": "node dist/app.js", - "clean": "rm -rf bower_components && rm -rf dist && rm -rf node_modules && rm -rf output" + "run": "node bundle.js", + "clean": "rm -rf bower_components && rm -rf bundle.js && rm -rf node_modules && rm -rf output" }, "license": "MIT", "devDependencies": { "purs-loader": "file:../", - "webpack": "^1.4.15" + "webpack": "^1.8.4" } } diff --git a/example/src/entry.js b/example/src/entry.js index 160bee4..cc09034 100644 --- a/example/src/entry.js +++ b/example/src/entry.js @@ -1,7 +1,7 @@ -var test = require('purs?output=output!./Test.purs'); +var test = require('./Test'); -var foo = require('purs?output=output!./Foo.purs'); +var foo = require('./Foo'); -var baz = require('purs?output=output!./Foo/Baz.purs'); +var baz = require('./Foo/Baz'); console.log(test, foo, baz); diff --git a/example/webpack.config.js b/example/webpack.config.js index 629138a..19997f3 100644 --- a/example/webpack.config.js +++ b/example/webpack.config.js @@ -1,16 +1,18 @@ var path = require('path'); -module.exports = { - entry: './src/entry', - output: { - path: path.join(__dirname, 'dist'), - filename: 'app.js' - }, - resolve: { - modulesDirectories: [ - 'node_modules', - 'web_modules', - 'output' - ] - } -}; +var config + = { entry: './src/entry' + , output: { path: __dirname + , filename: 'bundle.js' + } + , module: { loaders: [ { test: /\.purs$/, loader: 'purs-loader' } ] } + , resolve: { modulesDirectories: [ 'node_modules', + 'output' + ] + , extensions: ['', '.js', '.purs'] + } + , resolveLoader: { root: path.join(__dirname, 'node_modules') } + } + ; + +module.exports = config; diff --git a/package.json b/package.json index 56c6b39..d7e986f 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "build": "npm run-script build:compile && npm run-script build:package", "build:compile": "gulp build", "build:package": "./node_modules/.bin/webpack --progress --colors --profile --bail", - "build:json": "./node_modules/.bin/webpack --progress --colors --profile --bail --json > index.json" + "build:json": "./node_modules/.bin/webpack --progress --colors --profile --bail --json > index.json", + "prepublish": "npm run-script build" }, "files": [ "index.js" -- cgit v1.2.3 From 94a23e744896b4440794de5d6cffedff1a1a2d56 Mon Sep 17 00:00:00 2001 From: eric thul Date: Sun, 12 Apr 2015 11:33:04 -0400 Subject: Removing npmignore --- .npmignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .npmignore diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 90c978b..0000000 --- a/.npmignore +++ /dev/null @@ -1 +0,0 @@ -example/ -- cgit v1.2.3