diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..11cf2bf --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,17 @@ +module.exports = { + root: true, + env: { + node: true + }, + 'extends': [ + 'plugin:vue/essential', + 'eslint:recommended' + ], + parserOptions: { + parser: 'babel-eslint' + }, + rules: { + 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0dddc6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +.DS_Store +node_modules +/dist + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.purs-repl b/.purs-repl new file mode 100644 index 0000000..6802fc7 --- /dev/null +++ b/.purs-repl @@ -0,0 +1 @@ +import Prelude diff --git a/README.md b/README.md index 8f4ee84..96c7f15 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,18 @@ # pure-vue -Vue.js Purescript binding using Composition API + +## Project setup +``` +npm install +``` + +### Compiles and hot-reloads for development +1. spago install arrays +3. spago install record-extra +1. vue create my_cool_project +2. cd my_cool_project +3. spago init +4. npm install vue-cli-plugin-pure-vue +5. vue invoke vue-cli-plugin-pure-vue + +## Example Application +Check out Todo List [Example Application](https://https://github.com/smarwei/pure-vue-todo-example). diff --git a/generator/index.js b/generator/index.js new file mode 100644 index 0000000..3ca78a3 --- /dev/null +++ b/generator/index.js @@ -0,0 +1,34 @@ +module.exports = (api, options) => { + api.render('./templateExampleApp') + if(options.createVueConfigJs.toLowerCase() === 'y') { + api.render('./template') + } + + api.extendPackage({ + dependencies: { + '@vue/composition-api': '^0.5.0' + }, + devDependencies: { + "purs-loader": "^3.7.1" + } + }) + + api.injectImports(api.entryFile, `import VueCompositionApi from '@vue/composition-api'`) + + module.exports.hooks = (api) => { + api.afterInvoke(() => { + const { EOL } = require('os') + const fs = require('fs') + const contentMain = fs.readFileSync(api.resolve(api.entryFile), { encoding: 'utf-8' }) + const lines = contentMain.split(/\r?\n/g).reverse() + + if (lines.findIndex(line => line.match(/Vue\.use.*VueCompositionApi.*/)) !== -1) return + + const renderIndex = lines.findIndex(line => line.match(/import/)) + lines[renderIndex-1] += `${EOL}Vue.use(VueCompositionApi);${EOL}` + lines.reverse() + + fs.writeFileSync(api.entryFile, lines.join(EOL), { encoding: 'utf-8' }) + }) + } +} \ No newline at end of file diff --git a/generator/template/spago.dhall b/generator/template/spago.dhall new file mode 100644 index 0000000..1f32be7 --- /dev/null +++ b/generator/template/spago.dhall @@ -0,0 +1,16 @@ +{- +Welcome to a Spago project! +You can edit this file as you like. +-} +{ name = "my-project" +, dependencies = + [ "arrays" + , "console" + , "effect" + , "psci-support" + , "record-extra" + , "typelevel-prelude" + ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "node_modules/vue-cli-plugin-pure-vue/src/**/*.purs", "test/**/*.purs" ] +} diff --git a/generator/template/vue.config.js b/generator/template/vue.config.js new file mode 100644 index 0000000..a9ea1bd --- /dev/null +++ b/generator/template/vue.config.js @@ -0,0 +1,21 @@ +const path = require('path'); + +module.exports = { + chainWebpack: config => { + // Purescript Loader + config.module + .rule('purescript') + .test(/\.purs$/) + .use('purs-loader') + .loader('purs-loader') + .tap(() => ({ + // bundle: true, + // spago: true, + src: [ + path.join('src', '**', '*.purs'), + path.join('node_modules', 'vue-cli-plugin-pure-vue', 'src', '**', '*.purs'), + path.join('.spago', '**', 'src', '**', '*.purs'), + ], + })) + }, +} \ No newline at end of file diff --git a/generator/templateExampleApp/src/App.purs b/generator/templateExampleApp/src/App.purs new file mode 100644 index 0000000..ce0f427 --- /dev/null +++ b/generator/templateExampleApp/src/App.purs @@ -0,0 +1,52 @@ +module App where + +import Prelude +import Effect (Effect) +import Effect.Unsafe (unsafePerformEffect) +import Effect.Vue.Ref as Ref +import Effect.Vue.Hooks as Hooks +import Effect.Class.Console (logShow) +import Data.Array (snoc) + + +type Product = { id :: Int, title :: String, price :: Number } +createProduct :: Array Product -> Int -> String -> Number -> Array Product +createProduct products id title price = snoc products { id, title, price } + +execCreateProduct :: Ref.Ref (Array Product) -> String -> Number -> Effect Unit +execCreateProduct products title price = do + p <- Ref.read products + let p' = createProduct p 42 title (23.666) + Ref.write p' products + +incRelease :: Ref.Ref Int -> Effect Unit +incRelease release = Ref.modify_ (\x -> x + 1) release + +compTest :: Ref.Ref Int -> Effect Int +compTest release = do + r <- Ref.read release + pure (r + 1) + +type Props = {} + +setup :: forall a. Props -> Effect a +setup props = unsafePerformEffect do + products :: Ref.Ref (Array Product) <- Ref.new [] + showUserInfo <- Ref.new false + release <- Ref.new 9 + + computedTest <- Ref.computed $ compTest release + + Hooks.onMounted mounted + + Ref.vReturn { products + , showUserInfo + , release + , computedTest + , incRelease: (incRelease release) + , addProduct: (execCreateProduct products) + } + +mounted :: Effect Unit +mounted = do + logShow "estasetase" \ No newline at end of file diff --git a/generator/templateExampleApp/src/App.vue b/generator/templateExampleApp/src/App.vue new file mode 100644 index 0000000..318d4e1 --- /dev/null +++ b/generator/templateExampleApp/src/App.vue @@ -0,0 +1,31 @@ + + + diff --git a/generator/templateExampleApp/src/views/PrivacyPolicy.purs b/generator/templateExampleApp/src/views/PrivacyPolicy.purs new file mode 100644 index 0000000..00efb15 --- /dev/null +++ b/generator/templateExampleApp/src/views/PrivacyPolicy.purs @@ -0,0 +1,24 @@ +module PrivacyPolicy where + +import Prelude +import Effect (Effect) +import Effect.Unsafe (unsafePerformEffect) +import Effect.Vue.Ref as Ref +import Record.Extra as Record +import Data.Array as Arr +import Type.Row (RProxy(..)) + +type Props = ( release :: Int ) + +-- this to lib +-- type RecordProps = Record.Record Props +props = Record.keys (RProxy :: RProxy Props) + +privacyPolicy :: String -> String +privacyPolicy policyRaw = policyRaw + +buildSetup :: forall a. String -> _ -> Effect a +buildSetup policyRaw props = unsafePerformEffect do + dialog <- Ref.new true + + Ref.vReturn { dialog, privacyPolicy: privacyPolicy policyRaw } \ No newline at end of file diff --git a/generator/templateExampleApp/src/views/PrivacyPolicy.vue b/generator/templateExampleApp/src/views/PrivacyPolicy.vue new file mode 100644 index 0000000..1bac6fb --- /dev/null +++ b/generator/templateExampleApp/src/views/PrivacyPolicy.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/index.js b/index.js new file mode 100644 index 0000000..05876e9 --- /dev/null +++ b/index.js @@ -0,0 +1,2 @@ +module.exports = (api, options) => { +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d90e174 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "vue-cli-plugin-pure-vue", + "version": "0.1.0", + "description": "Purescript bindings for Vue.js using Compsition API", + "license": "Unlicense", + "repository": "smweiss/pure-vue", + "main": "index.js", + "keywords": [ + "vue.js", + "vue", + "purescript", + "binding", + "smweiss" + ], + "private": true, + "dependencies": { + }, + "devDependencies": { + } +} diff --git a/prompts.js b/prompts.js new file mode 100644 index 0000000..4a26e26 --- /dev/null +++ b/prompts.js @@ -0,0 +1,9 @@ +module.exports = [ + { + type: 'input', + name: 'createVueConfigJs', + message: 'Create vue.config.js (overrides if already existing)', + validate: input => ['Y', 'y', 'N', 'n'].includes(input), + default: 'Y' + }, +] \ No newline at end of file diff --git a/src/Props.purs b/src/Props.purs new file mode 100644 index 0000000..43964c4 --- /dev/null +++ b/src/Props.purs @@ -0,0 +1,28 @@ +module Props where + +import Prelude + + +type Props = Array PropType +data PropType = Int String + | String String + | Number String +-- | Object String + +toProp :: PropType -> String +toProp (Int s) = s +toProp (String s) = s +toProp (Number s) = s + +toProps :: Array PropType -> Array String +toProps = map toProp + + +-- class Proppable a where +-- toProps :: a -> Array String +-- +-- --instance propProps :: Proppable Props +-- -- toProps p = Arr.fromFoldable $ Record.keys (RProxy :: RProxy Props) +-- +-- type PropsRecord = Record Props +-- props = Arr.fromFoldable $ Record.keys (RProxy :: RProxy Props) \ No newline at end of file diff --git a/src/vueHooks.js b/src/vueHooks.js new file mode 100644 index 0000000..f9b87ec --- /dev/null +++ b/src/vueHooks.js @@ -0,0 +1,50 @@ +"use strict"; + +var comp = require("@vue/composition-api"); + +// exports.onMounted = vue.onMounted +exports.onBeforeMount = function(eff) { + return function() { + comp.on(eff) + } +} +exports.onMounted = function(eff) { + return function() { + comp.onMounted(eff) + } +} +exports.onBeforeUpdate = function(eff) { + return function() { + comp.on(eff) + } +} +exports.onUpdated = function(eff) { + return function() { + comp.on(eff) + } +} +exports.onBeforeUnmount = function(eff) { + return function() { + comp.on(eff) + } +} +exports.onUnmounted = function(eff) { + return function() { + comp.on(eff) + } +} +exports.onActivated = function(eff) { + return function() { + comp.on(eff) + } +} +exports.onDeactivated = function(eff) { + return function() { + comp.on(eff) + } +} +exports.onErrorCaptured = function(eff) { + return function() { + comp.on(eff) + } +} \ No newline at end of file diff --git a/src/vueHooks.purs b/src/vueHooks.purs new file mode 100644 index 0000000..e7c350e --- /dev/null +++ b/src/vueHooks.purs @@ -0,0 +1,35 @@ +module Effect.Vue.Hooks + ( onBeforeMount + , onMounted + , onBeforeUpdate + , onUpdated + , onBeforeUnmount + , onUnmounted + , onActivated + , onDeactivated + , onErrorCaptured + ) where + +import Prelude +import Effect (Effect) +import Effect.Class (class MonadEffect, liftEffect) + +-- These lifecycle registration methods can only be used during the invocation of a setup hook. +-- It automatically figures out the current instance calling the setup hook using internal global state. +-- It is intentionally designed this way to reduce friction when extracting logic into external functions. + +-- foreign import onMounted :: forall m a. MonadEffect m => a -> m Unit +foreign import onBeforeMount :: Effect Unit -> Effect Unit +foreign import onMounted :: Effect Unit -> Effect Unit +foreign import onBeforeUpdate :: Effect Unit -> Effect Unit +foreign import onUpdated :: Effect Unit -> Effect Unit +foreign import onBeforeUnmount :: Effect Unit -> Effect Unit +foreign import onUnmounted :: Effect Unit -> Effect Unit +foreign import onActivated :: Effect Unit -> Effect Unit +foreign import onDeactivated :: Effect Unit -> Effect Unit +foreign import onErrorCaptured :: Effect Unit -> Effect Unit + +-- What about +-- onRenderTracked +-- onRenderTriggered +-- ? \ No newline at end of file diff --git a/src/vueRef.js b/src/vueRef.js new file mode 100644 index 0000000..4de34ad --- /dev/null +++ b/src/vueRef.js @@ -0,0 +1,57 @@ +"use strict"; + +var comp = require("@vue/composition-api"); + +// Ref + +exports.vReturn = function(dict) { + return function() { + return dict + } +} + +exports.new = function (val) { + return function () { + return comp.ref(val); + }; +}; + +exports.read = function (ref) { + return function () { + return ref.value; + }; +}; + +exports.modifyImpl = function (f) { + return function (ref) { + return function () { + var t = f(ref.value); + ref.value = t.state; + return t.value; + }; + }; +}; + +exports.write = function (val) { + return function (ref) { + return function () { + ref.value = val; + return {}; + }; + }; +}; + + +// Computed + +exports.computed = function (fn) { + return function () { + return comp.computed(fn) + }; +}; + + +// to seperate module + +// exports.setup = function (props) { +// } \ No newline at end of file diff --git a/src/vueRef.purs b/src/vueRef.purs new file mode 100644 index 0000000..7265c1c --- /dev/null +++ b/src/vueRef.purs @@ -0,0 +1,57 @@ +module Effect.Vue.Ref + ( Ref + , new + , read + , modify' + , modify + , modify_ + , write + , vReturn + , computed + ) where + +import Prelude +import Effect (Effect) + +-- data Ref a = Ref a +-- foreign import ref :: forall a b. a -> Ref b +-- foreign import refVal :: forall a b. Ref a -> a + + +foreign import vReturn :: forall a b. a -> Effect b + + +-- | A value of type `Ref a` represents a mutable reference +-- | which holds a value of type `a`. +foreign import data Ref :: Type -> Type + +-- | Create a new mutable reference containing the specified value. +foreign import new :: forall s. s -> Effect (Ref s) + +-- | Read the current value of a mutable reference +foreign import read :: forall s. Ref s -> Effect s + +-- | Update the value of a mutable reference by applying a function +-- | to the current value. +modify' :: forall s b. (s -> { state :: s, value :: b }) -> Ref s -> Effect b +modify' = modifyImpl + +foreign import modifyImpl :: forall s b. (s -> { state :: s, value :: b }) -> Ref s -> Effect b + +-- | Update the value of a mutable reference by applying a function +-- | to the current value. The updated value is returned. +modify :: forall s. (s -> s) -> Ref s -> Effect s +modify f = modify' \s -> let s' = f s in { state: s', value: s' } + +modify_ :: forall s. (s -> s) -> Ref s -> Effect Unit +modify_ f s = void $ modify f s + +-- | Update the value of a mutable reference to the specified value. +foreign import write :: forall s. s -> Ref s -> Effect Unit + + +-- | +-- | COMPUTED +-- | + +foreign import computed :: forall a b. Effect a -> Effect (Ref b) \ No newline at end of file diff --git a/workspace.code-workspace b/workspace.code-workspace new file mode 100644 index 0000000..362d7c2 --- /dev/null +++ b/workspace.code-workspace @@ -0,0 +1,7 @@ +{ + "folders": [ + { + "path": "." + } + ] +} \ No newline at end of file