diff --git a/.gitignore b/.gitignore index ca822a63..3b5c49bc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ elm-stuff/ repl-temp-* .DS_Store */index.html -index.html \ No newline at end of file +index.html +node_modules + diff --git a/bin/style-elements.js b/bin/style-elements.js new file mode 100755 index 00000000..4656a828 --- /dev/null +++ b/bin/style-elements.js @@ -0,0 +1,56 @@ +#!/usr/bin/env node + +var styleElements = require("../"); +var pkg = require("../package.json"); +var program = require("commander"); +var fs = require("fs"); +var chalk = require("chalk"); +var requiredOptions = ["stylesheetModule", "stylesheetFunction", "mode", "output"]; +var utils = require("../js/utils"); +var writeFile = utils.writeFile; +var assertKeysPresent = utils.assertKeysPresent; + +var options = getOptions(process.argv, program); + +styleElements({ + stylesheetModule: options.stylesheetModule, + stylesheetFunction: options.stylesheetFunction, + mode: options.mode +}) + .then(result => writeFile(options.output, result)) + .then(() => { + console.warn( + chalk.green(`\n----> Success! styles were written to ${program.output}\n`) + ); + }); + +function getOptions(argv, program) { + program + .version(pkg.version) + .usage("[options] ") + .option( + "-o, --output [outputFile]", + "(optional) file to write the CSS to", + "out.css" + ) + .option( + "-m, --mode [layout/viewport]", + "(optional) whether to render stylesheet for 'layout' or 'viewport'", + "layout" + ) + .parse(argv); + + var options = { + stylesheetModule: program.args[0], + stylesheetFunction: program.args[1], + output: program.output, + mode: program.mode + }; + + assertKeysPresent(options, requiredOptions, _missingOptions => { + program.outputHelp(); + process.exit(1); + }); + + return options; +} diff --git a/index.js b/index.js new file mode 100644 index 00000000..246c4527 --- /dev/null +++ b/index.js @@ -0,0 +1,86 @@ +var path = require("path"); +var compileElm = require("node-elm-compiler").compile; +var utils = require("./js/utils.js"); + +var unindent = utils.unindent; +var writeFile = utils.writeFile; +var withTmpDir = utils.withTmpDir; +var assertKeysPresent = utils.assertKeysPresent; + +var requiredOptions = ["stylesheetModule", "stylesheetFunction", "mode"]; + +function generateCss(opts) { + assertKeysPresent(opts, requiredOptions, missingOptions => { + throw new Error(`Missing options: ${missingOptions.join(", ")}`); + }); + + return withTmpDir().then(tmpDirPath => { + var emitterSourceFile = path.join(tmpDirPath, "StyleElementsEmitter.elm"); + var emitterWorkerFile = path.join(tmpDirPath, "style-elements-emitter.js"); + var emitterTemplate = buildEmitterTemplate( + opts.stylesheetModule, + opts.stylesheetFunction, + opts.mode + ); + + return writeFile(emitterSourceFile, emitterTemplate) + .then(() => compile(emitterSourceFile, { output: emitterWorkerFile, yes: true })) + .then(() => extractCssResults(emitterWorkerFile)); + }); +} + +function buildEmitterTemplate(stylesheetModule, stylesheetFunction, mode) { + return unindent( + ` + port module StyleElementsEmitter exposing (..) + + import ${stylesheetModule} + import Element + + + port result : String -> Cmd msg + + + styles = + Element.${renderFunction(mode)} ${stylesheetModule}.${stylesheetFunction} + + + main : Program Never () Never + main = + Platform.program + { init = ( (), result styles ) + , update = \\_ _ -> ( (), Cmd.none ) + , subscriptions = \\_ -> Sub.none + } + ` + ); +} + +function renderFunction(mode) { + switch (mode) { + case "viewport": return "toViewportCss"; + case "layout": return "toLayoutCss"; + default: throw new Error(`Invalid mode: ${mode}, must be either 'layout' or 'viewport'`); + } +} + +function compile(src, options) { + return new Promise(function(resolve, reject) { + compileElm(src, options).on("close", function(exitCode) { + if (exitCode === 0) { + resolve(); + } else { + reject("Errored with exit code " + exitCode); + } + }); + }); +} + +function extractCssResults(destFile) { + var emitter = require(destFile).StyleElementsEmitter; + var worker = emitter.worker(); + + return new Promise(resolve => worker.ports.result.subscribe(resolve)); +} + +module.exports = generateCss; diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 00000000..1549c828 --- /dev/null +++ b/js/utils.js @@ -0,0 +1,48 @@ +var tmp = require("tmp"); +var fs = require("fs"); + +module.exports = { + unindent: function(text) { + var indentation = text.split("\n") + .map(line => { + var match = line.match(/([\s]+)[^\s]+/); + return match && match[1]; + }) + .find(value => value); + + return indentation + ? text.replace(new RegExp(`^${indentation}`, "gm"), "") + : text; + }, + + writeFile: function(path, content) { + return new Promise((resolve, reject) => { + return fs.writeFile(path, content, err => (err ? reject(err) : resolve())); + }); + }, + + withTmpDir: function() { + return new Promise(function(resolve, reject) { + tmp.dir({ unsafeCleanup: true }, function(err, tmpDirPath) { + if (err) { + reject(err); + } else { + resolve(tmpDirPath); + } + }); + }); + }, + + assertKeysPresent: function(object, requiredKeys, missingCallback) { + var providedKeys = Object.keys(object || {}); + var missingKeys = requiredKeys.filter(key => { + return ( + providedKeys.indexOf(key) === -1 || providedKeys[key] === "" + ); + }); + + if (missingKeys.length > 0) { + missingCallback(missingKeys); + } + } +}; diff --git a/package.json b/package.json new file mode 100644 index 00000000..2e24450d --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "style-elements", + "version": "3.2.3", + "description": "CSS Preprocoessor for Elm's style-elements", + "main": "index.js", + "bin": { + "style-elements": "./bin/style-elements.js" + }, + "files": [ + "js/utils.js" + ], + "dependencies": { + "chalk": "^2.0.1", + "commander": "^2.11.0", + "postcss": "^6.0.6", + "tmp": "0.0.31" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "http://package.elm-lang.org/packages/mdgriffith/style-elements" + }, + "author": "Matthew Griffith", + "license": "BSD-3-Clause" +} diff --git a/src/Element.elm b/src/Element.elm index a939371f..b8469393 100644 --- a/src/Element.elm +++ b/src/Element.elm @@ -62,6 +62,8 @@ module Element , layout , viewport , toHtml + , toLayoutCss + , toViewportCss , embedStylesheet , Device , classifyDevice @@ -196,7 +198,7 @@ Some convient elements for working with forms. ## Advanced Rendering -@docs toHtml, embedStylesheet +@docs toHtml, embedStylesheet, toLayoutCss, toViewportCss ### Deprecated @@ -1252,6 +1254,20 @@ toHtml stylesheet el = (Render.render stylesheet el) +{-| Renders the stylesheet generated by 'layout' to css +-} +toLayoutCss : StyleSheet style variation -> String +toLayoutCss stylesheet = + Render.layoutCss stylesheet + + +{-| Renders the stylesheet generated by 'viewport' to css +-} +toViewportCss : StyleSheet style variation -> String +toViewportCss stylesheet = + Render.viewportCss stylesheet + + {-| Embed a stylesheet. -} embedStylesheet : StyleSheet style variation -> Html msg diff --git a/src/Element/Internal/Render.elm b/src/Element/Internal/Render.elm index 4517944f..2c3defb2 100644 --- a/src/Element/Internal/Render.elm +++ b/src/Element/Internal/Render.elm @@ -45,15 +45,25 @@ root stylesheet elm = (embed False stylesheet :: render stylesheet elm) +viewportCss : Internal.StyleSheet elem variation -> String +viewportCss stylesheet = + normalizeFull () ++ stylesheet.css + + +layoutCss : Internal.StyleSheet elem variation -> String +layoutCss stylesheet = + normalize ++ stylesheet.css + + embed : Bool -> Internal.StyleSheet elem variation -> Html msg embed full stylesheet = Html.node "style" [] [ Html.text <| if full then - normalizeFull () ++ stylesheet.css + viewportCss stylesheet else - normalize ++ stylesheet.css + layoutCss stylesheet ]