Skip to content

Commit

Permalink
Add Webpacker (AlchemyCMS#1775)
Browse files Browse the repository at this point in the history
* Install webpacker and configure own instance

Following the official webpacker engines guide

* Add demo admin JS pack

Something to work with during testing

* Serve and compile Alchemy packs in host app

Serve the Alchemy packs via Rack::Static if public file server is enabled (ie. during development) and enhance the yarn:install and assets:precompile tasks so that the Alchemy packs get compiled asd well. Copy over the files into host apps public/ folder afterwards.

* Ensure that we run yarn install before installing Alchemy

* Ensure to also serve packs in tests

* Install node modules on GH CI

* Do not use our own webpacker instance in page preview

We want the webpacker instance of the host app in the preview frame.

* Use webpacker 5

* Add a prettier config

* Add a webpack-dev-server proxy

* Only enhance rake tasks that are present

If you install webpacker into a fresh Rails app there is not yarn:install task yet.

* Update Babel config and add core-js

* Add and configure Jest

* Run Jest specs in GH CI

* Convert i18n module into ES6

* Add favicon to assets

Sprockets complains that we need this file

* Enable Jest coverage reports for code climate

* Ignore unknown window messages

Now that we have Webpack installed the dev server emits messages on the window as well. Lets just ignore them.
  • Loading branch information
tvdeyen authored Apr 7, 2020
1 parent a43a746 commit e246432
Show file tree
Hide file tree
Showing 32 changed files with 691 additions and 78 deletions.
1 change: 1 addition & 0 deletions .browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
defaults
37 changes: 36 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,21 @@ jobs:
timeout-minutes: 10
run: |
bundle install --jobs 4 --retry 3 --path vendor/bundle
- name: Restore node modules cache
id: yarn-cache
uses: actions/cache@preview
with:
path: node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install yarn
run: yarn install
- name: Prepare database
run: |
bundle exec rake alchemy:spec:prepare
- name: Run tests & publish code coverage
uses: paambaati/codeclimate-action@v2.5.5
uses: paambaati/codeclimate-action@v2.5.6
env:
CC_TEST_REPORTER_ID: bca4349e32f97919210ac8a450b04904b90683fcdd57d65a22c0f5065482bc22
with:
Expand All @@ -99,3 +109,28 @@ jobs:
with:
name: Screenshots
path: spec/dummy/tmp/screenshots
Jest:
runs-on: ubuntu-latest
env:
NODE_ENV: test
steps:
- uses: actions/checkout@v1
- name: Restore node modules cache
uses: actions/cache@preview
with:
path: node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install yarn
run: yarn install
- name: Run jest
run: yarn jest
- name: Run jest & publish code coverage
uses: paambaati/codeclimate-action@v2.5.6
env:
CC_TEST_REPORTER_ID: bca4349e32f97919210ac8a450b04904b90683fcdd57d65a22c0f5065482bc22
with:
coverageLocations:
./coverage/lcov.info:lcov
coverageCommand: yarn jest --collectCoverage --coverageDirectory=coverage
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ spec/dummy/public/assets
.ruby-version
.env
.rspec
node_modules
yarn-error.log
yarn-debug.log*
.yarn-integrity
yarn.lock
/public/
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"semi": false,
"trailingComma": "none",
"vueIndentScriptAndStyle": true,
"arrowParens": "always"
}
1 change: 1 addition & 0 deletions alchemy_cms.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Gem::Specification.new do |gem|
gem.add_runtime_dependency 'simple_form', ['>= 4.0', '< 6']
gem.add_runtime_dependency 'sprockets', ['>= 3.0', '< 5']
gem.add_runtime_dependency 'turbolinks', ['>= 2.5']
gem.add_runtime_dependency 'webpacker', ['>= 4.0', '< 6']

gem.add_development_dependency 'capybara', ['~> 3.0']
gem.add_development_dependency 'capybara-screenshot', ['~> 1.0']
Expand Down
1 change: 1 addition & 0 deletions app/assets/config/alchemy_manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//= link alchemy/menubar.js
//= link alchemy/print.css
//= link alchemy/welcome.css
//= link alchemy/favicon.ico
//= link tinymce/plugins/alchemy_link/plugin.min.js
//= link tinymce/tinymce.min.js
//= link_directory ../stylesheets/tinymce/skins/alchemy/ .css
Expand Down
2 changes: 0 additions & 2 deletions app/assets/javascripts/alchemy/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
//= require alchemy/alchemy.growler
//= require alchemy/alchemy.gui
//= require alchemy/alchemy.hotkeys
//= require alchemy/alchemy.i18n
//= require alchemy/alchemy.image_cropper
//= require alchemy/alchemy.image_overlay
//= require alchemy/alchemy.string_extension
Expand All @@ -49,6 +48,5 @@
//= require alchemy/alchemy.spinner
//= require alchemy/alchemy.tinymce
//= require alchemy/alchemy.tooltips
//= require alchemy/alchemy.translations
//= require alchemy/alchemy.trash_window
//= require alchemy/page_select
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,6 @@ Alchemy.ElementEditors =
if data.message == 'Alchemy.focusElementEditor'
$element = $("#element_#{data.element_id}")
Alchemy.ElementEditors.focusElement($element)
else
console.warn 'Unknown message received!', data

onClickBody: (e) ->
element = $(e.target).parents('.element-editor')[0]
Expand Down
32 changes: 0 additions & 32 deletions app/assets/javascripts/alchemy/alchemy.i18n.js.coffee

This file was deleted.

29 changes: 0 additions & 29 deletions app/assets/javascripts/alchemy/alchemy.translations.js.coffee

This file was deleted.

11 changes: 11 additions & 0 deletions app/helpers/alchemy/admin/base_helper.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require "webpacker/helper"

module Alchemy
module Admin
# This module contains helper methods for rendering dialogs, toolbar buttons and confirmation windows.
Expand All @@ -14,6 +16,15 @@ module Admin
module BaseHelper
include Alchemy::BaseHelper
include Alchemy::Admin::NavigationHelper
include ::Webpacker::Helper

def current_webpacker_instance
if controller_name == 'pages' && action_name == 'show'
super
else
Alchemy.webpacker
end
end

# Returns a string showing the name of the currently logged in user.
#
Expand Down
70 changes: 70 additions & 0 deletions app/javascript/alchemy/admin/__tests__/i18n.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import translate from "../i18n"

describe("translate", () => {
describe("if Alchemy.locale is not set", () => {
it("Throws an error", () => {
expect(() => {
translate("help")
}).toThrow("Alchemy.locale is not set")
})
})

describe("if Alchemy.locale is set to a known locale", () => {
beforeEach(() => {
Alchemy.locale = "en"
})

describe("if translation is present", () => {
it("Returns translated string", () => {
expect(translate("help")).toEqual("Help")
})

describe("if key includes a period", () => {
describe("that is translated", () => {
it("splits into group", () => {
expect(translate("formats.date")).toEqual("Y-m-d")
})
})

describe("that is not translated", () => {
it("returns key", () => {
expect(translate("formats.lala")).toEqual("formats.lala")
})
})

describe("that has unknown group", () => {
it("returns key", () => {
expect(translate("foo.bar")).toEqual("foo.bar")
})
})
})

describe("if replacement is given", () => {
it("replaces it", () => {
expect(translate("allowed_chars", 5)).toEqual("of 5 chars")
})
})
})

describe("if translation is not present", () => {
it("Returns passed string", () => {
expect(translate("foo")).toEqual("foo")
})
})
})

describe("if Alchemy.locale is set to a unknown locale", () => {
beforeEach(() => {
Alchemy.locale = "kl"
})

it("Returns passed string and logs a warning", () => {
const spy = jest.spyOn(console, "warn").mockImplementation(() => {})
expect(translate("help")).toEqual("help")
expect(spy.mock.calls).toEqual([
["Translations for locale kl not found!"]
])
spy.mockRestore()
})
})
})
48 changes: 48 additions & 0 deletions app/javascript/alchemy/admin/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import translationData from "./translations"

const KEY_SEPARATOR = /\./

function currentLocale() {
if (Alchemy.locale == null) {
throw "Alchemy.locale is not set! Please set Alchemy.locale to a locale string in order to translate something."
}
return Alchemy.locale
}

function getTranslations() {
const locale = currentLocale()
const translations = translationData[locale]

if (translations) {
return translations
}
console.warn(`Translations for locale ${locale} not found!`)
return {}
}

function nestedTranslation(translations, key) {
const keys = key.split(KEY_SEPARATOR)
const group = translations[keys[0]]
if (group) {
return group[keys[1]] || key
}
return key
}

function getTranslation(key) {
const translations = getTranslations()

if (KEY_SEPARATOR.test(key)) {
return nestedTranslation(translations, key)
}
return translations[key] || key
}

export default function translate(key, replacement) {
let translation = getTranslation(key)

if (replacement) {
return translation.replace(/%\{.+\}/, replacement)
}
return translation
}
32 changes: 32 additions & 0 deletions app/javascript/alchemy/admin/translations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const translations = {
en: {
allowed_chars: "of %{count} chars",
cancel: "Cancel",
cancelled: "Cancelled",
click_to_edit: "click to edit",
complete: "Complete",
element_dirty_notice:
"This element has unsaved changes. Do you really want to fold it?",
help: "Help",
ok: "Ok",
page_dirty_notice:
"You have unsaved changes on this page. They will be lost if you continue.",
page_found: "Page found",
pages_found: "Pages found",
url_validation_failed: "The url has no valid format.",
warning: "Warning!",
"File is too large": "File is too large",
"File is too small": "File is too small",
"File type not allowed": "File type not allowed",
"Maximum number of files exceeded": "Maximum number of files exceeded.",
"Uploaded bytes exceed file size": "Uploaded bytes exceed file size",
formats: {
datetime: "Y-m-d H:i",
date: "Y-m-d",
time: "H:i",
time_24hr: false
}
}
}

export default translations
12 changes: 12 additions & 0 deletions app/javascript/packs/alchemy/admin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import translate from "alchemy/admin/i18n"

// Global Alchemy object
if (typeof window.Alchemy === "undefined") {
window.Alchemy = {}
}

// Global utility method for translating a given string
//
Alchemy.t = (key, replacement) => {
return translate(key, replacement)
}
1 change: 1 addition & 0 deletions app/views/layouts/alchemy/admin.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
</script>
<%= render 'alchemy/admin/partials/routes' %>
<%= javascript_include_tag('alchemy/admin/all', 'data-turbolinks-track' => true) %>
<%= javascript_pack_tag('alchemy/admin') %>
<%= yield :javascript_includes %>
</head>
<%= content_tag :body, id: 'alchemy', class: alchemy_body_class do %>
Expand Down
Loading

0 comments on commit e246432

Please sign in to comment.