{"id":19537592,"url":"https://github.com/elixircl/surface-tailwind-sass","last_synced_at":"2025-02-26T04:27:35.650Z","repository":{"id":130381705,"uuid":"546354037","full_name":"ElixirCL/surface-tailwind-sass","owner":"ElixirCL","description":"Ejemplo de Configuración de Phoenix con Surface, Tailwind y Sass","archived":false,"fork":false,"pushed_at":"2022-10-06T18:09:48.000Z","size":499,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-08T18:13:52.804Z","etag":null,"topics":["hacktoberfest"],"latest_commit_sha":null,"homepage":"https://elixircl.github.io/surface-tailwind-sass","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ElixirCL.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-06T00:28:56.000Z","updated_at":"2022-10-06T17:24:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"0ee7a6eb-4820-4080-889f-4811a522dff1","html_url":"https://github.com/ElixirCL/surface-tailwind-sass","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ElixirCL%2Fsurface-tailwind-sass","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ElixirCL%2Fsurface-tailwind-sass/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ElixirCL%2Fsurface-tailwind-sass/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ElixirCL%2Fsurface-tailwind-sass/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ElixirCL","download_url":"https://codeload.github.com/ElixirCL/surface-tailwind-sass/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240791947,"owners_count":19858355,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest"],"created_at":"2024-11-11T02:28:35.093Z","updated_at":"2025-02-26T04:27:35.609Z","avatar_url":"https://github.com/ElixirCL.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":":doctype: book\n:encoding: utf-8\n:numbered:\n:source-linenums-option:\n:username: elixircl\n:source: https://github.com/ElixirCL/surface-tailwind-sass\n:producer: elixircl.github.io\n:copyright: CC-BY-NC-SA 4.0\n:lang: es\n:description: Un proyecto de ejemplo de Phoenix con Surface, Tailwind y Sass.\n:keywords: elixir, programación, lenguaje, referencia\n:imagesdir: images\n:front-cover-image: docs/assets/cover.png\n:epub-chapter-level: 2\n:toc: left\n:toclevels: 3\n:toc-title: Tabla de Contenidos\n:source-highlighter: highlight.js\n:highlightjs-languages: elixir, javascript, bash, sh, lua, c, txt, html, yaml, toml, json, rust\n:ext-relative:\n//:stylesheet: style.css\n\n# Surface + Tailwind + Sass\n\nRealizaremos una configuración básica de https://surface-ui.org/[Surface UI], https://tailwindcss.com/[Tailwind] y https://sass-lang.com/[Sass] en un proyecto https://www.phoenixframework.org/[Phoenix con LiveView].\n\nEscrito por https://ninjas.cl[Camilo Castro] y https://github.com/elixircl/surface-tailwind-sass/graphs/contributors[colaboradores]. \nPara https://elixircl.github.io[Elixir Chile].\n\nA menos que se especifique explícitamente, los contenidos de ésta obra están bajo una http://creativecommons.org/licenses/by-nc-sa/4.0/[Licencia Creative Commons Atribución-No-Comercial-Compartir-Igual 4.0 Internacional]\nhttp://creativecommons.org/licenses/by-nc-sa/4.0/[image:https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png[Licencia Creative Commons]]. \n\nEl código fuente está bajo la licencia BSD. https://github.com/elixircl/surface-tailwind-sass/\n\ntoc::[]\n\n## Creación de un nuevo proyecto\n\nVamos a crear un nuevo proyecto, aunque si ya tienes uno previo igual es útil, puesto que realizaremos\nlas configuraciones de forma manual para entender como funciona todo.\n\nLlamaremos al proyecto _\"miapp\"_, como solo es una prueba no necesitaremos Ecto (base de datos) ni Mailer (envío de correos).\n\n```sh\n$ mix phx.new miapp --no-ecto --no-mailer\n```\n\n## mix.exs\n\nVamos a nuestro archivo `mix.exs` y agregamos las deps y sus configuraciones.\n\nPrimero añadir `:surface` a la lista de compiladores\n\n```elixir\ndef project do\n    # [ ...\n        compilers: [:surface] ++ Mix.compilers(),\n    # ] ...\nend\n```\n\nAñadimos la función `catalogues()` para cargar los catálogos de Surface.\n\n```elixir\ndefp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\ndefp elixirc_paths(:dev), do: [\"lib\"] ++ catalogues()\ndefp elixirc_paths(_), do: [\"lib\"]\n\ndefp catalogues do\n    [\n        \"priv/catalogue\",\n        \"deps/surface/priv/catalogue\"\n    ]\nend\n```\n\nLuego añadimos las dependencias (aprovechamos de añadir `credo` igual para tener un linter).\n\n```elixir\ndefp deps do\n    # [...\n        {:tailwind, \"~\u003e 0.1\", runtime: Mix.env() == :dev},\n        {:dart_sass, \"~\u003e 0.4\", runtime: Mix.env() == :dev},\n        {:surface, \"~\u003e 0.8\"},\n        {:surface_catalogue, \"~\u003e 0.5.1\"},\n        {:credo, \"~\u003e 1.6\"},\n    # ]\nend\n```\n\nFinalmente configuramos los comandos a usar con mix\n\n```elixir\ndefp aliases do\n    [\n        setup: [\"deps.get\", \"cmd npm --prefix=./assets i\"],\n        lint: [\"format\", \"credo --strict\"],\n        test: [\"test\"],\n        server: [\"assets.deploy\", \"phx.server\"],\n        \"assets.clean\": [\"phx.digest.clean --all\"],\n        \"assets.deploy\": [\n            \"sass default --no-source-map\",\n            \"tailwind default --minify\",\n            \"esbuild default --minify\",\n            \"phx.digest priv/static -o priv/public\"\n        ]\n    ]\nend\n```\n\n- `setup`: instala las dependencias de elixir y de javascript.\n- `lint`: formatea y evalúa que el código cumpla con los estándares de credo.\n- `test`: ejecuta las pruebas.\n- `server`: compila los assets y luego ejecuta el servidor.\n- `assets.clean`: limpia los archivos generados por phx.digest\n- `assets.deploy`: El órden es importante. Primero compila los archivos `scss`, luego los combina con tailwind, sigue el compilar los archivos _javascript_ del proyecto. Finalmente se copia los archivos generados desde el directorio `static` al directorio `public`.\n\nCuando este configurado nuestro `mix.exs` podemos ejecutar `mix deps.get`\npara instalar las dependencias.\n\nimage:https://user-images.githubusercontent.com/292738/194191092-507e70c0-cfb4-4d9e-9565-0ac8414d3edc.png[mix deps.get]\n\n## .formatter.exs\n\nAñadiremos la configuración para el comando `mix format`\n\n```elixir\n[\n    # import_deps: [:ecto, :phoenix, :surface],\n    import_deps: [:phoenix, :surface],\n    plugins: [Phoenix.LiveView.HTMLFormatter, Surface.Formatter.Plugin],\n    inputs: [\"*.{leex,heex,ex,exs,sface}\", \"priv/*/seeds.exs\", \"{config,lib,test}/**/*.{leex,heex,ex,exs,sface}\"],\n    subdirectories: [\"priv/*/migrations\"]\n]\n```\n\n## .gitignore\n\nAprovechamos de agregar algunas reglas para ignorar ciertos archivos de surface a nuestro archivo `.gitignore`:\n\n```text\n.DS_Store\n_hooks/\n_components.css\n```\n\n### .vscode/settings.json\n\nSi usas _VSCode_ puedes añadir la siguiente configuración:\n\n\n```json\n{\n    \"scss.lint.unknownAtRules\": \"ignore\",\n    \"files.associations\": {\n        \"*.css\": \"tailwindcss\"\n    }\n}\n```\n\n## Directorio assets/\n\nEste directorio tendrá los archivos `js` y `css` que luego serán\nprocesados por `tailwind`, `sass` y `esbuild`.\n\n### package.json\nCrearemos un archivo llamado `package.json` donde podremos\nincluir las dependencias de javascript que necesitemos en el proyecto.\n\nPor el momento, solamente pondremos un archivo simple sin dependencias.\n\n```json\n{\n    \"private\": true,\n    \"devDependencies\": {\n        \"autoprefixer\": \"^9.8.0\"\n    },\n    \"engines\": {\n        \"npm\": \"\u003e=6.0.0\",\n        \"node\": \"\u003e=14.0.0\"\n    }\n}\n```\n### postcss.config.js\n\nEl contenido puede ser similar a lo siguiente:\n\n```js\nmodule.exports = {\n    plugins: [\n        require('tailwindcss'),\n        require('autoprefixer'),\n    ],\n}\n```\n\nMás info en https://postcss.org/\n\n### tailwind.config.js\n\nEl contenido puede ser similar a lo siguiente:\n\n```js\nmodule.exports = {\n  important: true,\n  content: [\n    \"../lib/**/*.{ex,leex,heex,eex,sface}\",\n    \"./js/_hooks/**/*.js\",\n    \"./js/app.js\"\n  ],\n}\n```\n\nPara más detalles se puede ver la página https://tailwindcss.com/docs/configuration\n\n### css/app.scss\n\nCabe destacar que utilizar https://sass-lang.com[Sass] es completamente\nopcional y hasta innecesario si se realiza una configuración apropiada de _postcss_. Revisar cómo usando acá https://tailwindcss.com/docs/using-with-preprocessors\n\nCrearemos un archivo nuevo llamado `css/app.scss` que simplemente cambia el color del background.\n\n```css\n$color: purple;\nbody {\n    background-color: $color;\n}\n```\n\nAprovecharemos de eliminar los archivos:\n\n- `app.css`\n- `phoenix.css`\n\n### js/app.js\n\nVamos al archivo `js/app.js` y eliminamos la importación de los estilos\ncss:\n\n```js\n// import \"../css/app.css\"\n```\n\nY agregamos los Hooks creados por Surface\n\n```js\nimport Hooks from \"./_hooks\"\n// ...\nlet liveSocket = new LiveSocket(\"/live\", Socket, { hooks: Hooks, ... })\n```\n\n## Directorio config/\n\nVamos a usar dos configuraciones específicas. Una será la de producción\nque guardará y aglomerará (digest) los archivos js y css. Guardará los archivos en el directorio `priv/static`. La otra guardará en un directorio llamado\n`priv/public` que será usado principalmente para desarrollo (para tener autoreload) y evitar el caché.\n\n### config/config.exs\n\nVamos a configurar las opciones predeterminadas. Esta configuración guardará los archivos en `priv/static`.\n\nPrimero añadimos que todos los assets serán entregados desde la ruta\n`/static`\n\n```elixir\nconfig :miapp, MiappWeb.Endpoint,\n  # ... \n  static_url: [path: \"/static\"]\n```\n\nAhora configuramos tanto _Tailwind_ como _Sass_ (debajo de la config de esbuild)\n\n```elixir\n# esbuild\nconfig :esbuild,\n  version: \"0.14.29\",\n  default: [\n    args:\n      ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),\n    cd: Path.expand(\"../assets\", __DIR__),\n    env: %{\"NODE_PATH\" =\u003e Path.expand(\"../deps\", __DIR__)}\n  ]\n\n# sass\nconfig :dart_sass,\n    version: \"1.39.0\",\n    default: [\n        args: ~w(--load-path=./node_modules css/app.scss ../priv/static/assets/app-raw.css),\n        cd: Path.expand(\"../assets\", __DIR__)\n    ]\n\n# tailwind\nconfig :tailwind,\n    version: \"3.0.7\",\n        default: [\n            args: ~w(\n                --config=tailwind.config.js\n                --input=../priv/static/assets/app-raw.css\n                --output=../priv/static/assets/app.css\n            ),\n            cd: Path.expand(\"../assets\", __DIR__)\n        ]\n```\n\n### config/dev.exs\n\nÉsta configuración guardará los archivos en `priv/public`. Sobre escribe las configuraciones y rutas de `config.exs`.\n\n```elixir\nconfig :esbuild,\n  version: \"0.14.29\",\n  default: [\n    args: ~w(js/app.js --bundle --target=es2017 --outdir=../priv/public/assets),\n    cd: Path.expand(\"../assets\", __DIR__),\n    env: %{\"NODE_PATH\" =\u003e Path.expand(\"../deps\", __DIR__)}\n  ],\n  catalogue: [\n    args: ~w(../deps/surface_catalogue/assets/js/app.js --bundle --target=es2017 --minify --outdir=../priv/public/assets/catalogue),\n    cd: Path.expand(\"../assets\", __DIR__),\n    env: %{\"NODE_PATH\" =\u003e Path.expand(\"../deps\", __DIR__)}\n  ]\n\n# agregamos la configuracion de los catálogos de surface\n# para que pueda encontrar los estilos, ya que modificamos\n# la ruta de fábrica para los assets.\nconfig :surface_catalogue,\n  assets_path: \"/static/assets/catalogue/\"\n\nconfig :dart_sass,\n  version: \"1.39.0\",\n  default: [\n    args: ~w(--load-path=./node_modules css/app.scss ../priv/public/assets/app-raw.css),\n    cd: Path.expand(\"../assets\", __DIR__)\n  ]\n\nconfig :tailwind,\n  version: \"3.0.7\",\n  default: [\n    args: ~w(\n    --config=tailwind.config.js\n    --input=../priv/public/assets/app-raw.css\n    --output=../priv/public/assets/app.css\n  ),\n    cd: Path.expand(\"../assets\", __DIR__)\n  ]\n```\n\nLuego configuramos el arreglo de `watchers` para verificar cuando\nse ha cambiado un archivo y volver a compilarlo. Incluyendo\nlos archivos javascript, sass y surface.\n\n```elixir\nconfig :miapp, MiappWeb.Endpoint,\n    # ...\n    watchers: [\n        esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},\n        esbuild: {Esbuild, :install_and_run, [:catalogue, ~w(--sourcemap=inline --watch)]},\n        sass: {\n            DartSass,\n            :install_and_run,\n            [:default, ~w(--embed-source-map --source-map-urls=absolute --watch)]\n        },\n        tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}\n    ]\n```\n\nFinalmente configuramos los formatos de archivo que serán recompilados\n\n```elixir\nconfig :miapp, MiappWeb.Endpoint,\n    reloadable_compilers: [:phoenix, :elixir, :surface],\n    live_reload: [\n        patterns: [\n            # ...\n            ~r\"priv/public/.*(js|css|png|jpeg|jpg|gif|svg)$\",\n            ~r\"priv/catalogue/.*(ex)$\",\n            ~r\"lib/miapp_web/(live|views|components)/.*(ex|js)$\",\n            ~r\"lib/miapp_web/live/.*(sface)$\",\n            # ...\n        ]\n]\n```\n\n## Directorio lib/miapp_web\n\nEn este directorio irán las configuraciones de los sistemas que sirven\nlos requests desde el navegador y renderizan html.\n\n### endpoint.ex\n\nNecesitamos configurar el archivo `endpoint.ex` para permitir\nque los assets sean servidos desde nuestro directorio especial.\n\n```elixir\n    # ...\n    socket \"/live\", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]\n    # ...\n\n    plug Plug.Static,\n        at: \"/\",\n        from: :miapp,\n        gzip: false,\n        only: ~w(favicon.ico robots.txt)\n    \n    plug Plug.Static,\n        at: \"/static\",\n        from: {:miapp, \"priv/public\"},\n        gzip: false,\n        only: ~w(assets fonts images)\n```\n\n### router.ex\n\nImportamos las funciones de _Surface_ para utilizarlas\nen nuestras rutas.\n\n```elixir\ndefmodule MiappWeb.Router do\n    use MiappWeb, :router\n    import Surface.Catalogue.Router\n    # ...\n```\n\nLuego añadimos la ruta a nuestra página de index\nllamada `live/home.ex`.\n\n\n```elixir\n# ...\nscope \"/\", MiappWeb do\n    pipe_through :browser\n\n    live_session :default do\n        live \"/\", Live.Home, :index\n    end\nend\n# ...\n```\n\nFinalmente añadimos la ruta para acceder a los catálogos \nde _Surface_, solamente cuando estemos en ambiente de \ndesarollo.\n\n```elixir\nif Mix.env() == :dev do\n    scope \"/\" do\n        pipe_through :browser\n        surface_catalogue \"/catalogue\"\n    end\nend\n```\n\n### live/home.ex\n\nCrearemos un archivo llamado `live/home.ex` para renderizar un html simple usando el siguiente contenido:\n\n```elixir\ndefmodule MiappWeb.Live.Home do\n  use MiappWeb, :surface_live_view\n\n  @impl true\n  def render(assigns) do\n    ~F\"\"\"\n    \u003cdiv class={\"bg-slate-100\"}\u003e\n      \u003cdiv class={\"text-sky-500\"}\u003e\n        \u003ch1 class={\"text-lg\", \"font-medium\"}\u003eEsta es Mi App\u003c/h1\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n    \"\"\"\n  end\nend\n```\n\n### miapp_web.ex\n\nComo podemos notar estamos llamando a `surface_live_view` \npara importar un código global. Ésto nos permitirá \nsimplificar el código, reutilizando la importación.\n\nAñadimos lo siguiente a `miapp_web.ex`:\n\n```elixir\ndef surface_live_view do\n    quote do\n        use Surface.LiveView,\n            layout: {MiappWeb.LayoutView, \"live.html\"}\n        unquote(view_helpers())\n    end\nend\n```\n\n## Probando\n\nSi todo sale como esta previsto, solo bastaría ejecutar el comando\n\n`mix server`\n\npara hacer el deploy de los assets y ejecutar el servidor.\n\nAl cual podremos acceder desde http://localhost:4000\n\n### Página Principal\n\nimage:https://user-images.githubusercontent.com/292738/194343605-0871c3ae-fcd6-4417-82f2-c1aa26e9743a.png[]\n\n### Catálogo de Surface\n\nimage:https://user-images.githubusercontent.com/292738/194342378-40b55e29-0828-4674-a671-c42986a7a1ad.png[]\n\n\n## Creditos\n\n++++\nMade with \u003ci class=\"fa fa-heart\"\u003e\u0026#9829;\u003c/i\u003e by \u003ca href=\"https://ninjas.cl\" target=\"_blank\"\u003eNinjas.cl\u003c/a\u003e.\n++++\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felixircl%2Fsurface-tailwind-sass","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felixircl%2Fsurface-tailwind-sass","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felixircl%2Fsurface-tailwind-sass/lists"}