├── .gitignore ├── images ├── github-watch.png ├── sennajs-flip.png ├── sennajs-navigate.png ├── sennajs-overview.png ├── sennajs-content-extract.png ├── dxp-javascript-loader-settings.png ├── liferay-npm-scripts-pipeline.png └── github-allow-edits-from-maintainers.png ├── css ├── images │ ├── example_1.jpg │ └── example_2.jpg ├── how_to_name_the_css_files.md ├── when_to_use_css_utility_classes.md ├── when_and_how_to_create_a_new_css_component.md ├── how_to_write_responsive_css.md └── how_to_name_the_css_classes.md ├── .github ├── semantic.yml ├── ISSUE_TEMPLATE │ ├── Question.md │ ├── Proposal.md │ └── devDependency.md └── workflows │ └── ci.yml ├── .prettierrc.json ├── .eslintrc.js ├── package.json ├── CONTRIBUTING.md ├── dxp ├── browser_detection.md ├── session.md ├── liferay_frontend_component.md ├── requests.md ├── formatting.md ├── aspect_oriented_programming.md ├── drag_and_drop.md ├── debounce_and_throttle.md ├── urls.md ├── frontend_taglib_clay.md ├── exporting.md ├── liferay_component.md ├── linting.md ├── debugging.md ├── liferay_global_event_bus.md ├── how_clay_arrives_to_dxp.md ├── how_to_use_css_custom_properties.md ├── clay-css-3-intro.md ├── dev_dependencies.md ├── aui_script.md └── javascript_minification.md ├── general ├── testing │ ├── anatomy.md │ ├── supported_libraries.md │ └── ui_components.md ├── typescript.md ├── react.md ├── linking.md ├── javascript.md ├── comments.md ├── security.md ├── commit_messages.md ├── naming.md ├── glossary.md ├── formatting.md ├── branch_structure.md ├── react_dnd.md ├── file_names.md ├── imports.md └── pull_requests.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .deploy 2 | .publish 3 | .temp 4 | dist 5 | node_modules 6 | temp -------------------------------------------------------------------------------- /images/github-watch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liferay/liferay-frontend-guidelines/HEAD/images/github-watch.png -------------------------------------------------------------------------------- /images/sennajs-flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liferay/liferay-frontend-guidelines/HEAD/images/sennajs-flip.png -------------------------------------------------------------------------------- /css/images/example_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liferay/liferay-frontend-guidelines/HEAD/css/images/example_1.jpg -------------------------------------------------------------------------------- /css/images/example_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liferay/liferay-frontend-guidelines/HEAD/css/images/example_2.jpg -------------------------------------------------------------------------------- /images/sennajs-navigate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liferay/liferay-frontend-guidelines/HEAD/images/sennajs-navigate.png -------------------------------------------------------------------------------- /images/sennajs-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liferay/liferay-frontend-guidelines/HEAD/images/sennajs-overview.png -------------------------------------------------------------------------------- /images/sennajs-content-extract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liferay/liferay-frontend-guidelines/HEAD/images/sennajs-content-extract.png -------------------------------------------------------------------------------- /images/dxp-javascript-loader-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liferay/liferay-frontend-guidelines/HEAD/images/dxp-javascript-loader-settings.png -------------------------------------------------------------------------------- /images/liferay-npm-scripts-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liferay/liferay-frontend-guidelines/HEAD/images/liferay-npm-scripts-pipeline.png -------------------------------------------------------------------------------- /images/github-allow-edits-from-maintainers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liferay/liferay-frontend-guidelines/HEAD/images/github-allow-edits-from-maintainers.png -------------------------------------------------------------------------------- /.github/semantic.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Configuration for the Semantic Pull Request bot. 3 | # https://github.com/probot/semantic-pull-requests 4 | 5 | allowMergeCommits: true 6 | 7 | titleAndCommits: true 8 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": false, 3 | "endOfLine": "lf", 4 | "jsxSingleQuote": true, 5 | "singleQuote": true, 6 | "tabWidth": 4, 7 | "trailingComma": "none", 8 | "useTabs": true 9 | } 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true 4 | }, 5 | extends: 'liferay', 6 | parserOptions: { 7 | ecmaVersion: 2018 8 | }, 9 | rules: { 10 | 'no-for-of-loops/no-for-of-loops': 'off' 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🤔 Question 3 | about: Issues for asking questions about our frontend practices 4 | labels: question 5 | --- 6 | 7 | 12 | 13 | ## What is your question? 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Proposal.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💍 Proposal 3 | about: Issues for proposing a change to our frontend practices 4 | labels: rfc 5 | --- 6 | 7 | 12 | 13 | ## What is your proposal? 14 | 15 | ## Why would adopting this proposal be beneficial? 16 | 17 | ## What are the alternatives to this proposal? 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/devDependency.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 📦 devDependency 3 | about: Issues about adding new devDependencies 4 | labels: dev-dependency 5 | --- 6 | 7 | 13 | 14 | ## What is the new or updated dependency? 15 | 16 | ## Why is this dependency useful to you and others? 17 | 18 | ## What are the alternatives? 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Based on: https://github.com/actions/starter-workflows/blob/master/ci/node.js.yml 2 | 3 | name: ci 4 | 5 | on: 6 | push: 7 | branches: [ master ] 8 | pull_request: 9 | branches: [ master ] 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [12.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: yarn --frozen-lockfile 27 | - run: yarn ci 28 | env: 29 | CI: true 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Liferay Frontend Guidelines", 3 | "devDependencies": { 4 | "eslint": "7.9.0", 5 | "eslint-config-liferay": "21.1.0", 6 | "prettier": "^2.0.3" 7 | }, 8 | "name": "liferay-frontend-guidelines", 9 | "private": true, 10 | "repository": "https://github.com/liferay/liferay-frontend-guidelines.git", 11 | "scripts": { 12 | "ci": "yarn format:check && yarn lint && yarn test", 13 | "format": "prettier --write \"**/{.,}*.js{,on}\" \"**/*.md\"", 14 | "format:changed": "git ls-files -mz -- \"*.js\" \"*.json\" \"*.md\" | xargs -0 prettier --write --", 15 | "format:check": "prettier --list-different \"**/{.,}*.js{,on}\" \"**/*.md\"", 16 | "lint": "eslint \"**/*.js\"", 17 | "lint:fix": "eslint --fix \"**/*.js\"", 18 | "test": "node support/checkLinks.js" 19 | }, 20 | "version": "0.0.1" 21 | } 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/CONTRIBUTING.md). 2 | 3 | # Contributing to Liferay frontend projects 4 | 5 | In order to make the guidance in this document easy to cite from other projects, it is split up into the following sections: 6 | 7 | - [Commit message format](general/commit_messages.md) 8 | - [Linking](general/linking.md) 9 | -------------------------------------------------------------------------------- /dxp/browser_detection.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/dxp/browser_detection.md). 2 | 3 | # Browser detection 4 | 5 | Browser detection can be useful sometimes but should be avoided. 6 | 7 | To detect the current browser, you can use the _global_ [`Liferay.Browser`](https://github.com/liferay/liferay-portal/blob/eb823a315efd5a85c2e08abea8de3c9362bb07d7/portal-web/docroot/html/common/themes/top_js.jspf#L21-L102) object and its various functions. 8 | 9 | You'll notice that this `Liferay.Browser` object actually wraps [`BrowserSnifferUtil`](https://github.com/liferay/liferay-portal/blob/ced3d6d93c8721ae09ea2c2c88ee8aec4dd36938/portal-kernel/src/com/liferay/portal/kernel/servlet/BrowserSnifferUtil.java), meaning that both the server (java) and client (js) will be in "sync". 10 | -------------------------------------------------------------------------------- /general/testing/anatomy.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/general/testing/anatomy.md). 2 | 3 | # Anatomy of a test 4 | 5 | > 🚧 This document is a work-in-progress. 6 | 7 | ## Start `it()` descriptions with a verb, not with "should" 8 | 9 | Descriptions that start with "should" can usually be rewritten more concisely and without loss of information by dropping the "should" and starting with an appropriate verb. This is useful because it reduces the likelihood we'll have to introduce an ugly linebreak that harms readability: 10 | 11 | ### Example 12 | 13 | Write this: 14 | 15 | ```javascript 16 | it('applies default settings if none are given', () => { 17 | // ... 18 | }); 19 | ``` 20 | 21 | instead of: 22 | 23 | ```javascript 24 | it('should apply default settings if none are given', () => { 25 | // ... 26 | }); 27 | ``` 28 | 29 | ### Enforcement 30 | 31 | [eslint-config-liferay](https://github.com/liferay/eslint-config-liferay) provide a custom lint rule, [liferay/no-it-should](https://github.com/liferay/eslint-config-liferay/blob/master/plugins/eslint-plugin-liferay/docs/rules/no-it-should.md), to guard against the use of "should" at the start of `it()` descriptions. It is active by default in all projects that use eslint-config-liferay. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/README.md). 2 | 3 | # Liferay Frontend Guidelines 4 | 5 | ![](https://github.com/liferay/liferay-frontend-guidelines/workflows/ci/badge.svg) 6 | 7 | This is a live (changing) repository containing [general](/general) and [Liferay DXP-specific](/dxp) guidelines for doing Frontend Development at Liferay Inc. 8 | 9 | ## Guideline development 10 | 11 | We don't have a formalized procedure for implementation changes, but informally, we expect most changes to evolve like this: 12 | 13 | 1. Somebody submits a question (eg. an issue) or proposal (eg. could be an issue or PR). 14 | 2. Over a period of some days, we arrive at (or draw near to) a consensus. 15 | 3. After an interval that gives people adequate time to respond, we conclude the discussion; ultimately someone like [@jbalsas](https://github.com/jbalsas) will have the ability to make a call about what the conclusion should be (if it's not clear, or if there is some other reason to veto the consensus). 16 | 17 | Once a conclusion is reached, the proposal can be marked with the "[resolved](https://github.com/liferay/liferay-frontend-guidelines/labels/resolved)" label. 18 | 19 | 4. PR documenting the guideline gets merged. 20 | 5. If possible to enforce via automation, we add linting or other enforcement at the appropriate location (for example, via [eslint-config-liferay](https://github.com/liferay/eslint-config-liferay) or tooling that is specific to [liferay-portal](https://github.com/liferay)). 21 | -------------------------------------------------------------------------------- /general/typescript.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/general/typescript.md). 2 | 3 | # TypeScript 4 | 5 | ## Can we use TypeScript in Liferay projects? 6 | 7 | We are currently trialing [TypeScript](https://www.typescriptlang.org/) in [the Clay project](https://github.com/liferay/clay). Clay is a well-isolated dependency that [liferay-portal](https://github.com/liferay/liferay-portal) consumes in the form of built NPM packages. This means that the language choice inside Clay is somewhat of an implementation detail, and liferay-portal itself doesn't require any TypeScript-specific tooling in order work with it. 8 | 9 | In the future we will [evaluate the possibility](https://github.com/liferay/liferay-frontend-guidelines/issues/37) of using TypeScript directly inside liferay-portal. 10 | 11 | For other projects, we should weigh up the relative costs and benefits of using TypeScript. For example, in projects that don't have build processes — examples include [liferay-js-themes-toolkit](https://github.com/liferay/liferay-js-themes-toolkit) and [liferay-npm-tools](https://github.com/liferay/liferay-npm-tools) — we've found that the ability to iterate and debug in-place (even inside the "node_modules") has been a useful debugging workflow that would be made more difficult if we switched to TypeScript, so we've stayed with untranspiled vanilla JavaScript so far. 12 | 13 | When using Typescript and React, check out [react-typescript-cheatsheet](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet) for helpful tips. 14 | -------------------------------------------------------------------------------- /dxp/session.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/dxp/session.md). 2 | 3 | # Session API 4 | 5 | The Session API provides a mechanism for associating persisted key/value pairs with a user as they navigate between pages. 6 | 7 | It consists of two methods exported from `frontend-js-web`: 8 | 9 | - [getSessionValue(key)](https://github.com/liferay/liferay-portal/blob/2d2e8ae74e9e7b33a07328277da64f54db576a04/modules/apps/frontend-js/frontend-js-web/src/main/resources/META-INF/resources/liferay/util/session.es.js#L38-L63) 10 | - [setSessionValue(key, Value)](https://github.com/liferay/liferay-portal/blob/2d2e8ae74e9e7b33a07328277da64f54db576a04/modules/apps/frontend-js/frontend-js-web/src/main/resources/META-INF/resources/liferay/util/session.es.js#L65-L85) 11 | 12 | ## Setting 13 | 14 | `setSessionValue(key, data)` makes a request to the server to associate `data` with `key` in the user's session. If `data` is a non-falsy object, it will be encoded with `JSON.stringify()` and prefixed with a special marker, `serialize://`, to flag it for deserialization on later retrieval. Otherwise, it gets transmitted as-is. 15 | 16 | ## Getting 17 | 18 | `getSessionValue(key)` retrieves string data from the user's session by making a fetch request to the server at a [`portal/session_click` endpoint](https://github.com/liferay/liferay-portal/blob/2d2e8ae74e9e7b33a07328277da64f54db576a04/modules/apps/frontend-js/frontend-js-web/src/main/resources/META-INF/resources/liferay/util/session.es.js#L34-L36). If the retrieved data begins with the `serialize://` prefix, it is stripped off and the remaining text is parsed as JSON using `JSON.parse()`. Otherwise, the data string is returned as-is. 19 | -------------------------------------------------------------------------------- /general/testing/supported_libraries.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/general/testing/supported_libraries.md). 2 | 3 | # Testing 4 | 5 | ## Supported libraries 6 | 7 | ### Supported libraries inside Liferay DXP 8 | 9 | As described in [Developer Dependencies](../../dxp/dev_dependencies.md), we use a very specific list of libraries within [Liferay DXP](https://github.com/liferay/liferay-portal). At the time of writing, for testing, we use: 10 | 11 | - [liferay-npm-scripts](https://github.com/liferay/liferay-npm-tools/tree/master/packages/liferay-npm-scripts) is our main entry point, and is called from the "test" script in each `package.json` file as `liferay-npm-scripts test`. 12 | - [Jest](https://jestjs.io/) is our test runner. 13 | - We bundle a number of libraries with liferay-npm-scripts that help us write user-centric tests; you don't need to specify these in your `devDependencies` (and indeed, [you shouldn't](../../dxp/dev_dependencies.md)) because they are available in all Yarn workspaces: 14 | - [@testing-library/jest-dom](https://testing-library.com/docs/ecosystem-jest-dom) 15 | - [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro) 16 | - [@testing-library/user-event](https://testing-library.com/docs/ecosystem-user-event) 17 | 18 | ### Supported libraries outside Liferay DXP 19 | 20 | Historically, we've used a broader range of test-related tooling in our other open source projects (in addition to the above: [Mocha](https://mochajs.org/), [Sinon](https://sinonjs.org/), [Karma](https://karma-runner.github.io/latest/index.html) etc). Moving forward, we'd like to converge as much as is practical and possible towards a similar set of dependencies. 21 | -------------------------------------------------------------------------------- /dxp/liferay_frontend_component.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/dxp/liferay_frontend_component.md). 2 | 3 | # Demystifying `liferay-frontend:component` tag 4 | 5 | We can use this tag for removing JavaScript code from JSPs. This tag accepts a `module` property through which We can pass a relative path for the script to be loaded. Also, this tag automatically registers the component with [Liferay’s component global registry](https://github.com/liferay/liferay-frontend-guidelines/blob/bc6dae8514af04a6384a7bea9d4ddf266087ffc5/dxp/liferay_component.md#register), depending on the `componentId` property. When using this tag, `namespace`(or `portletNamespace`) and `spritemap` props will become available. 6 | 7 | For example, in the JSP page: 8 | 9 | ```jsp 10 | ... 11 | 15 | ... 16 | ``` 17 | 18 | And when exporting the `myComponent.js` file we can define a default function there like: 19 | 20 | ```js 21 | export default function myComponent({namespace, spritemap, ...props}) { 22 | // ... code that uses namespace, spritemap etc 23 | } 24 | ``` 25 | 26 | This tag can be very handy if you want to use modern JS features, since we don’t have support for transpiling modern JS from taglibs. 27 | 28 | Also, this tag adds some treatment for handling JavaScript in our JSPs like [adding special treatment for our built-in SPA framework](https://github.com/liferay/liferay-portal/blob/815f48f484351e18b61e4b9c9fbf40f0609bdc56/modules/apps/frontend-taglib/frontend-taglib/src/main/java/com/liferay/frontend/taglib/servlet/taglib/ComponentTag.java#L225). 29 | 30 | All the content will be injected on the page using `AUI_SCRIPT_DATA` already explained [here](https://github.com/liferay/liferay-frontend-guidelines/blob/bc6dae8514af04a6384a7bea9d4ddf266087ffc5/dxp/resource_injection.md#scriptdata). 31 | -------------------------------------------------------------------------------- /dxp/requests.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/dxp/requests.md). 2 | 3 | # Requests 4 | 5 | Making requests is a common task in the frontend world. 6 | 7 | In [`liferay-portal`](https://github.com/liferay/liferay-portal/), we provide the [`fetch`](https://github.com/liferay/liferay-portal/blob/0b6a12f3b3dad0cb001b15e396cd46f586c96df5/modules/apps/frontend-js/frontend-js-web/src/main/resources/META-INF/resources/liferay/util/fetch.es.js) function which is a very thin wrapper on top of the existing [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) function implemented in modern browsers. Additionally, a [polyfill](https://github.com/liferay/liferay-portal/blob/0b6a12f3b3dad0cb001b15e396cd46f586c96df5/modules/apps/frontend-compatibility/frontend-compatibility-ie/build.gradle#L351) is also served for Internet Explorer 11, to ensure the `fetch` function is defined. 8 | 9 | ### Usage 10 | 11 | Make sure you add the [`frontend-js-web`](https://github.com/liferay/liferay-portal/tree/0b6a12f3b3dad0cb001b15e396cd46f586c96df5/modules/apps/frontend-js/frontend-js-web) dependency to your `package.json` file, and that the version corresponds to the one declared in the [`frontend-js-web/bnd.bnd`](https://github.com/liferay/liferay-portal/blob/0b6a12f3b3dad0cb001b15e396cd46f586c96df5/modules/apps/frontend-js/frontend-js-web/bnd.bnd#L3) file. 12 | 13 | ``` 14 | "dependencies": { 15 | ... 16 | "frontend-js-web": "4.0.0", 17 | ... 18 | }, 19 | ``` 20 | 21 | ### Use case: making a request to an endpoint that returns JSON 22 | 23 | ``` 24 | import {fetch} from 'frontend-js-web'; 25 | 26 | const url = 'http://localhost:8080/path/to/an/existing/endpoint'; 27 | 28 | fetch(url) 29 | .then(res => res.json()) 30 | .then(res => { 31 | // Use the data returned by the request 32 | }); 33 | ``` 34 | 35 | _NOTE_: In case you aren't using modules (i.e. `import`), you can access the `fetch` wrapper through the `Liferay.Util.fetch` method. 36 | 37 | ## More information. 38 | 39 | Please visit the MDN documentation on the [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API for more information. 40 | -------------------------------------------------------------------------------- /dxp/formatting.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/dxp/formatting.md). 2 | 3 | # Formatting 4 | 5 | Please, read our [General Formatting Guidelines](../general/formatting.md) for a complete overview of our formatting recommendations and best practices. 6 | 7 | [Prettier](https://prettier.io/) usage is built-in inside [Liferay DXP](https://github.com/liferay/liferay-portal) thanks to the [`liferay-npm-scripts`](https://github.com/liferay/liferay-npm-tools/tree/master/packages/liferay-npm-scripts) package that encapsulates all the necessary logic, dependencies and configuration for it to work. 8 | 9 | > Only JSP, JavaScript and SCSS files are formatted using Prettier 10 | 11 | Note that because we apply some small Liferay-specific overrides to Prettier's behavior when formatting in [liferay-portal](https://github.com/liferay/liferay-portal), please see the [editor integrations information](https://github.com/liferay/liferay-npm-tools/tree/master/packages/liferay-npm-scripts#editor-integrations) for details on how to set up your editor or IDE to apply these overrides automatically. 12 | 13 | Every module inside [Liferay DXP](https://github.com/liferay/liferay-portal) with JavaScript or SCSS files should contain a `package.json` file with at least the following npm scripts: 14 | 15 | ```javascript 16 | { 17 | "scripts": { 18 | "checkFormat": "liferay-npm-scripts check", 19 | "format": "liferay-npm-scripts fix" 20 | } 21 | } 22 | ``` 23 | 24 | ## format 25 | 26 | Among [other things](./linting.md), the `format` script will run Prettier with the default configuration and save the changes to your files. It can be invoked either directly as `yarn format` or through the Gradle Wrapper as `gradlew npmRunFormat`. 27 | 28 | ## checkFormat 29 | 30 | The `checkFormat` script will run Prettier (and some other checks described in "[Linting](./linting.md)") without saving your files and exit with an error if there are changes. This task is meant to run on CI to prevent files from being committed without the proper formatting. 31 | 32 | # See also 33 | 34 | - [Linting](./linting.md): information on checking and fixing the non-presentational aspects of our source code. 35 | -------------------------------------------------------------------------------- /general/react.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/general/react.md). 2 | 3 | # React 4 | 5 | ## Avoid using array indices for the `key` attribute 6 | 7 | A common anti-pattern in React components is the unnecessary use of array indices as `key` values: 8 | 9 | ```javascript 10 | 11 | {products.map(({cost, description, name, sku}, i) => ( 12 | 19 | ))} 20 | 21 | ``` 22 | 23 | It's best to avoid index-based `key` values — which are merely _positional_ — whenever you have an alternative that is more closely related to the _identity_ of an element. In example above, the [SKU](https://en.wikipedia.org/wiki/Stock_keeping_unit) ("Stock Keeping Unit") is a unique identifier and would be a great choice for the `key` attribute. 24 | 25 | The reason is that React uses the `key` as a hint to figure out how to best reconcile the DOM in a minimal way when changes are made. Imagine that your have a list of fruits which you render in two different states: 26 | 27 | | First render | Second render | 28 | | ------------ | ------------- | 29 | | 1. Apple | 1. Apple | 30 | | 2. Banana | 2. Banana | 31 | | 3. Pear | 3. Pear | 32 | | 4. Orange | 4. Apricot | 33 | | 5. Apricot | 5. Peach | 34 | | 6. Peach | | 35 | 36 | Consider what happens during the second render, having filtered the list so that "Orange" won't get rendered any more: 37 | 38 | - If you use the _names_ as keys, React can figure out that item "4. Orange" should be removed from the DOM, which is a single operation. 39 | - If you use the _indices_ as keys, React thinks you're telling it that item 4's name changed from "Orange" to "Apricot", item 5's name changed from "Apricot" to "Peach", and item 6 was removed, which is three operations. 40 | 41 | For much more detail, see [the React docs on reconciliation](https://reactjs.org/docs/reconciliation.html#recursing-on-children): 42 | 43 | > As a last resort, you can pass an item's index in the array as a key. This can work well if the items are never reordered, but reorders will be slow. 44 | -------------------------------------------------------------------------------- /dxp/aspect_oriented_programming.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/dxp/aspect_oriented_programming.md). 2 | 3 | # Aspect Oriented Programming (AOP) guidelines 4 | 5 | AOP provides tools to improve control of what is executed before and after another method is invoked. You can find this component in the [`frontend-js-web`](https://github.com/liferay/liferay-portal/tree/c0c13433600398fed8768f539aa8212978f7409c/modules/apps/frontend-js/frontend-js-web) module. 6 | 7 | See more about Aspect Oriented Programming in [this article](https://medium.com/@kyuwoo.choi/sneak-peek-to-javascript-aop-16458f807842). 8 | 9 | ## Prerequisites 10 | 11 | First of all the `frontend-js-web` dependency should be added to the `package.json` of the module in which we'll use AOP. Be sure that the version matches the one in [`frontend-js-web/bnd.bnd`](https://github.com/liferay/liferay-portal/blob/c0c13433600398fed8768f539aa8212978f7409c/modules/apps/frontend-js/frontend-js-web/bnd.bnd). 12 | 13 | ``` 14 | "dependencies": { 15 | ... 16 | "frontend-js-web": "4.0.0", 17 | ... 18 | }, 19 | ``` 20 | 21 | ## Use case: Change the return value of an instance method invocation 22 | 23 | ```javascript 24 | import {AOP} from `frontend-js-web`; 25 | 26 | const foo = new Foo(); 27 | foo.bar(); // "bar" 28 | 29 | const _interceptedFoo = AOP.after(target => { 30 | return AOP.alterReturn("modified bar"); 31 | }, foo, 'bar'); 32 | 33 | foo.bar(); // "modified bar" 34 | 35 | handle.detach(); // We can detach the interception 36 | 37 | foo.bar(); // "bar" 38 | ``` 39 | 40 | ## Use case: Preventing an instance method from being invoked 41 | 42 | ```javascript 43 | import {AOP} from `frontend-js-web`; 44 | 45 | const foo = new Foo(); 46 | foo.bar(); // "bar" 47 | 48 | const _interceptedFoo = AOP.before(target => { 49 | return AOP.prevent(); 50 | }, foo, 'bar'); 51 | 52 | foo.bar(); // undefined 53 | 54 | handle.detach(); // We can detach the interception 55 | 56 | foo.bar(); // "bar" 57 | ``` 58 | 59 | ## Use case in portal 60 | 61 | You can find an example of use of AOP in [FragmentsEditorDragDrop.es.js](https://github.com/liferay/liferay-portal/blob/c0c13433600398fed8768f539aa8212978f7409c/modules/apps/layout/layout-content-page-editor-web/src/main/resources/META-INF/resources/js/utils/FragmentsEditorDragDrop.es.js). 62 | -------------------------------------------------------------------------------- /general/linking.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/general/linking.md). 2 | 3 | # Linking 4 | 5 | ## Use "fixed" links for code 6 | 7 | When linking to code on GitHub, you can press the "y" key to obtain a "fixed" link (ie. one that includes a SHA-1 hash instead of a symbolic name like `master`). 8 | 9 | We recommend that you favor such "fixed" links for use in documentation and also within code comments that contain links, because "fixed" links never break (unless history is rewritten, which we generally avoid). This is in contrast to normal links, which can totally break (ie. lead to a 404) when a file is moved, or produce confusion when lines are moved within a file. 10 | 11 | More specifically: 12 | 13 | - **If linking to a specific _line_:** _Always_ use a fixed link. 14 | - **If linking to a specific _file_:** 15 | - **If it is _possible_ that the file might be renamed:** (eg. _any_ source code file) Use a fixed link. 16 | - **If it is _extremely unlikely_ that a file will be renamed:** (eg. a _top-level_ `package.json` or a _top-level_ `README.md`) Don't use a fixed link. 17 | 18 | Note the subtlety in that last point: in a monorepo directories can be reorganized, and package locations or names can be changed, so not even package-level `README.md` files can really be considered safe. The list of files that we can consider to be "extremely unlikely" to move is actually quite short. 19 | 20 | ### See also 21 | 22 | The counter-argument to using "fixed" links is that symbolic links (eg. to `master`) tend to stay "up-to-date". In other words, if you click on a link in years-old documentation, you'll see current code instead of years-old code. In the discussions about this trade-off (linked to below), we concluded in favor of "fixed" links. We place a greater value on the documentation being coherent (ie. linking to stable artifacts) than possibly more up-to-date, because it is very easy to switch to master when one wants to see the current state of a file, but not so easy to find the target of a broken reference: 23 | 24 | - https://github.com/liferay/liferay-frontend-guidelines/pull/29#discussion_r293012635 25 | - https://github.com/liferay/liferay-frontend-guidelines/pull/201#discussion_r487035366 26 | - https://github.com/liferay/liferay-frontend-guidelines/pull/226#discussion_r499599667 27 | -------------------------------------------------------------------------------- /general/javascript.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/general/javascript.md). 2 | 3 | # JavaScript 4 | 5 | ## Language features 6 | 7 | We have three major language environments in use across our projects: 8 | 9 | - In projects such as [Clay](https://github.com/liferay/clay) (and, at the time of writing, [the liferay-js-toolkit "develop" branch](https://github.com/liferay/liferay-js-toolkit/tree/develop)) we are starting to use [TypeScript](./typescript.md). 10 | - In projects such as [liferay-js-themes-toolkit](https://github.com/liferay/liferay-js-themes-toolkit) and [liferay-npm-tools](https://github.com/liferay/liferay-npm-tools) we use vanilla (untranspiled) JavaScript. These are command-line tools and, for consistency, we assume the availability of language features that are available in the version of [NodeJS](https://nodejs.org/en/) that is used by [liferay-portal](https://github.com/liferay/liferay-portal) (currently, v10.15.1). 11 | - In most other projects, including [liferay-portal](https://github.com/liferay/liferay-portal) itself, we use [Babel](https://babeljs.io); see the following section for a description of which transforms we use. 12 | 13 | ## Babel features 14 | 15 | Our goal in using Babel is to allow developers to write "modern JS" without having to dwell on platform specific limitations. Our main constraint is that we expect the transpiled code to work on all environments defined in the [Liferay DXP Compatibility Matrix](https://web.liferay.com/services/support/compatibility-matrix), which in practice means "modern browsers plus IE 11". 16 | 17 | In order to avoid churn, we don't make use of experimental transforms, but rather wait until [proposals have reached "stage 4"](https://github.com/tc39/proposals/blob/master/finished-proposals.md) of [the TC39 process](https://tc39.es/process-document/) before enabling them. In rare cases, we may include stage 3 proposals that are exceptionally useful (eg. we enabled [`babel-plugin-proposal-class-properties`](https://babeljs.io/docs/en/babel-plugin-proposal-class-properties) in [liferay-npm-scripts](https://github.com/liferay/liferay-npm-tools/blob/40cb21e25c0314f7b9629d42d8eb1195d0f11d28/packages/liferay-npm-scripts/src/config/babel.json#L4) because it is so broadly relied upon within the [React](https://reactjs.org) ecosystem). As seen in [this TypeScript issue](https://github.com/microsoft/TypeScript/issues/27644), in practice, stage 3 proposals are unlikely to change but sometimes they do, so caution is warranted. 18 | -------------------------------------------------------------------------------- /dxp/drag_and_drop.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/dxp/drag_and_drop.md). 2 | 3 | # Drag and Drop 4 | 5 | ## Drag and Drop in React 6 | 7 | We are currently using the [react-dnd](https://www.npmjs.com/package/react-dnd) package in [liferay-portal](https://github.com/liferay/liferay-portal), and we provide a shared copy as part of the [frontend-js-react-web](https://github.com/liferay/liferay-portal/blob/master/modules/apps/frontend-js/frontend-js-react-web/package.json) package. 8 | 9 | We chose it because it powerful, flexible, and sufficiently low-level that it can be adapted to address all the use cases we've encountered so far. If you find a scenario in which it is lacking, please [open an issue](https://github.com/liferay/liferay-frontend-guidelines/issues/new/choose) in the [liferay-frontend-guidelines](https://github.com/liferay/liferay-frontend-guidelines) repo so that we can evaluate how to best meet that use case. 10 | 11 | ### See also 12 | 13 | - The [react-dnd documentation](http://react-dnd.github.io/react-dnd/about). 14 | - Our [initial discussion](https://github.com/liferay/liferay-frontend-guidelines/issues/39) about drag-and-drop strategy, which shows how we settled on using react-dnd. 15 | - To see example usages, [search for "react-dnd"](https://github.com/liferay/liferay-portal/search?q=react-dnd&unscoped_q=react-dnd) in liferay-portal. Some sample hits follow (but note, at the time of writing the methods used are considered to be "Legacy Decorator API" — react-dnd's current top-level API is based on [hooks](https://reactjs.org/docs/hooks-overview.html) such as [`useDrag`](http://react-dnd.github.io/react-dnd/docs/api/use-drag), [`useDrop`](http://react-dnd.github.io/react-dnd/docs/api/use-drop) and so on): 16 | - [Defining a drop target](https://github.com/liferay/liferay-portal/blob/57706198c739266efdecdc01962d5ec344f31aeb/modules/apps/segments/segments-web/src/main/resources/META-INF/resources/js/components/criteria_builder/EmptyDropZone.es.js#L105-L116). 17 | - [Defining a drag source](https://github.com/liferay/liferay-portal/blob/57706198c739266efdecdc01962d5ec344f31aeb/modules/apps/segments/segments-web/src/main/resources/META-INF/resources/js/components/criteria_sidebar/CriteriaSidebarItem.es.js#L96-L105). 18 | - [Declaring a drag and drop context](https://github.com/liferay/liferay-portal/blob/f6ba16826f74c51d7cb28b30214b7554b8b12a57/modules/apps/segments/segments-web/src/main/resources/META-INF/resources/js/components/criteria_builder/ContributorsBuilder.es.js#L266). 19 | -------------------------------------------------------------------------------- /dxp/debounce_and_throttle.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/dxp/debounce_and_throttle.md). 2 | 3 | # `debounce()` and `throttle()` 4 | 5 | `debounce()` and `throttle()` are _higher-order functions_: 6 | 7 | > Functions that take other functions as arguments, and whose return values are functions too. 8 | 9 | Their purpose is to limit the rate at which an underlying function — usually an expensive one — is invoked in response to events. The goal is to turn an expensive set of operations into a cheap one: 10 | 11 | ```javascript 12 | const expensive = () => { /* lots of computation! */ }; 13 | 14 | const cheaper = debounce(expensive, ...); 15 | 16 | const render = () => ; 17 | ``` 18 | 19 | In this example `cheaper()` is a "debounced" version of `expensive()`: you can type in the `` component as fast as you like without saturating the processor. 20 | 21 | ## When to use `debounce()`? 22 | 23 | Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called. As in "execute this function only if 100 milliseconds have passed without it being called." 24 | 25 | Perhaps a function is called 1,000 times in a quick burst, dispersed over 3 seconds, then stops being called. If you have debounced it at 100 milliseconds, the function will only fire once, at 3.1 seconds, once the burst is over. Each time the function is called during the burst it resets the debouncing timer. 26 | 27 | Typical use case is in an autocomplete, where you just want to, for example, fetch autocomplete results after some time has passed after the last input. 28 | 29 | ## When to use `throttle()`? 30 | 31 | Throttling ensures that a function not be called more often than every `interval` milliseconds. 32 | 33 | Popular use cases are "resize" and "scroll" event listeners. 34 | 35 | In those use cases you don't want to use `debounce()` because that would delay providing feedback to the user until the interaction is over (and the interaction could last indefinitely). Rather, you want to _slow_ the rate of updates but not stop them entirely, so throttle is what you want. 36 | 37 | This in in contrast to the autocomplete use case we mentioned above for `debounce()`: in that scenario, ongoing interaction is likely to _invalidate_ any work you start doing during the interaction, so you only want to start working once the user has paused long enough to make it meaningful. 38 | 39 | ## Usage 40 | 41 | The `frontend-js-web` dependency should be added to the `package.json` of the module in which we'll use `debounce()` or `throttle()`: 42 | 43 | ``` 44 | "dependencies": { 45 | ... 46 | "frontend-js-web": "*", 47 | ... 48 | }, 49 | ``` 50 | 51 | You can then import and use the functions in your code: 52 | 53 | ```javascript 54 | import {debounce, throttle} from 'frontend-js-web'; 55 | 56 | const INTERVAL_MS = 100; 57 | 58 | const handleResize = throttle((event) => { 59 | /* handler logic */ 60 | }, INTERVAL_MS); 61 | 62 | const handleChange = debounce((event) => { 63 | /* handler logic */ 64 | }, INTERVAL_MS); 65 | ``` 66 | -------------------------------------------------------------------------------- /dxp/urls.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/dxp/urls.md). 2 | 3 | # URLs 4 | 5 | Constructing URLs is a common task in the frontend world. 6 | 7 | Previously, we made use of the [metal-uri](https://github.com/metal/metal-plugins/tree/eabc06702f498722ca3c32f0d19f441c14221d1d/packages/metal-uri) module. 8 | 9 | We now recommend using the native [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) API. 10 | 11 | Make sure you read the documentation referenced above to make sure you fully understand the URL API. 12 | 13 | Additionally, a polyfill is also served for Internet Explorer 11, to ensure the `URL` API can be used correcly. 14 | 15 | ### Use case: creating a relative URL 16 | 17 | ```javascript 18 | // Note: To create relative URLs the second argument of the URL constructor is required. 19 | const baseUrl = 'http://localhost:8080'; 20 | const guestUrl = new URL('/web/guest', baseUrl); 21 | console.log(`The guest URL is ${guestUrl}`); // The guest URL is http://localhost:8080/web/guest 22 | ``` 23 | 24 | ### Use case: creating an absolute URL 25 | 26 | ```javascript 27 | const portalUrl = new URL('http://localhost:8080'); 28 | console.log(`The portal URL is ${portalUrl}`); // The portal URL is http://localhost:8080 29 | ``` 30 | 31 | ### Use case: adding or setting parameters to a URL 32 | 33 | ```javascript 34 | const myUrl = new URL('http://localhost:8080'); 35 | myUrl.searchParams.set('foo', 0); 36 | myUrl.searchParams.set('bar', 'baz'); 37 | console.log(myUrl.toString()); // http://localhost:8080/?foo=0&bar=baz 38 | ``` 39 | 40 | ### Use case: appending a parameter in a URL 41 | 42 | You might need to set multiple values for the same parameter. 43 | 44 | While there is no definitive standard, most web frameworks allow multiple values to be associated with a single field (e.g. `field1=value1&field1=value2&field2=value3`). 45 | 46 | In order to acheive this behaviour, you can use the `append` method of the [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) interface. 47 | 48 | ```javascript 49 | const myUrl = new URL('http://localhost:8080/?foo=baz'); 50 | myUrl.searchParams.append('foo', 'another-baz'); 51 | console.log(myUrl.toString()); // http://localhost:8080/?foo=baz&foo=another-baz 52 | ``` 53 | 54 | ### Use case: checking for parameters in a URL 55 | 56 | ```javascript 57 | const myUrl = new URL('http://localhost:8080/?foo=baz&bar=qux'); 58 | 59 | myUrl.searchParams.has('foo')); // true 60 | myUrl.searchParams.has('bar')); // true 61 | myUrl.searchParams.has('not-there'); // false 62 | ``` 63 | 64 | ### Use case: getting the value of a parameter in a URL 65 | 66 | ```javascript 67 | const myUrl = new URL('http://localhost:8080/?foo=baz&bar=qux'); 68 | myUrl.searchParams.get('foo'); // 'baz' 69 | myUrl.searchParams.get('bar'); // 'qux' 70 | myUrl.searchParams.get('not-there'); // null; 71 | ``` 72 | 73 | ### Use case: deleting a parameter in a URL 74 | 75 | ```javascript 76 | const myUrl = new URL('http://localhost:8080/?foo=baz&bar=qux'); 77 | myUrl.searchParams.delete('foo'); 78 | console.log(myUrl.toString()); // http://localhost:8080/?bar=qux 79 | ``` 80 | 81 | ## More information. 82 | 83 | Please visit the MDN documentation on the [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) API for more information. 84 | -------------------------------------------------------------------------------- /dxp/frontend_taglib_clay.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/dxp/frontend_taglib_clay.md). 2 | 3 | # What is `frontend-taglib-clay` and its relation with `clay-*`, `@clayui/*`, and `clay-css`? 4 | 5 | ## High-level: 6 | 7 | `frontend-taglib-clay` provides a collection of Clay dependencies from npm and custom components to be used across DXP as taglibs and JS dependencies. Versions of these npm modules are locked in this module, this is done to keep modules consistent across DXP. By locking these versions, this means we use the same version of `@clayui/button` across all of DXP so that we can reduce the number of modules served with DXP. 8 | 9 | ## Structure: 10 | 11 | - `resources/META-INF/resources`: These are the custom JS components 12 | - `resources/META-INF/liferay-clay.tld`: This is the list of taglib definitions that can be used across DXP. 13 | - `package.json`: Lists all the provided versions of `clay-*` and `@clayui/*` packages. 14 | 15 | ## How it works: 16 | 17 | For example, if you want to use the version of `@clayui/button` specified in `liferay-portal` or a Clay taglib, you need to declare a Gradle dependency on `frontend-taglib-clay`. 18 | 19 | ### `build.gradle` 20 | 21 | ```gradle 22 | dependencies { 23 | //.... 24 | compileOnly project(":apps:frontend-taglib:frontend-taglib-clay") 25 | } 26 | ``` 27 | 28 | After declaring the Gradle dependency, you then can use one of the Clay npm modules like so: 29 | 30 | - `package.json` - Add the dependency you want, in this case `"@clayui/button": "3.4.0"` 31 | - Use the dependency as normal within your JS components 32 | 33 | For using the taglib: 34 | 35 | - `init.jsp` - Add tag dependency `<%@taglib uri="http://liferay.com/tld/clay" prefix="clay" %>` 36 | - Use as normal in JSP 37 | 38 | ```java 39 | 43 | ``` 44 | 45 | ### Caveats 46 | 47 | Some of the taglib definitions provide the `propsTransformer` property. This prop is unique to DXP and gives the user the ability to manipulate data that is provided to the React component within the taglib. See [content-dashboard](https://github.com/liferay/liferay-portal/blob/master/modules/apps/content-dashboard/content-dashboard-web/src/main/resources/META-INF/resources/view.jsp#L207) for example of use. 48 | 49 | ## Updating `frontend-taglib-clay`: 50 | 51 | Whenever there is a new version of `@clayui/*` or `clay-*` modules, we update all the versions in this module so that the most up to date version is used across DXP. 52 | 53 | ### Steps 54 | 55 | Below is the process of how we update this module with new dependency versions. 56 | 57 | 1. Update all `@clayui/*` and `clay-*` dependencies in `frontend-taglib-clay`'s package.json to the latest version. 58 | - Running `npx ncu '/@clayui/' -u` and `npx ncu '/clay-/' -u` will quickly update Clay dependencies to the latest version. 59 | 2. Navigate to `./portal-impl` and run `ant format-source-all` 60 | - This command will update _ALL_ uses of these dependencies across DXP. If `@clayui/button` is used in some other module, this command will update that module to use the latest version and keep it in sync with `frontend-taglib-clay`. 61 | 3. Run `yarn` from `./modules` 62 | - This is used to update the `yarn.lock` file for DXP. 63 | 4. Run `npx yarn-deduplicate yarn.lock` 64 | - This is run to remove and unneccessary duplications in the `yarn.lock` file 65 | 66 | After running through these steps, all `@clayui/*` and `clay-*` dependencies should now be synced across DXP at the same version. 67 | -------------------------------------------------------------------------------- /css/how_to_name_the_css_files.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/css/how_to_name_the_css_files.md). 2 | 3 | # How to name the CSS files? 4 | 5 | To understand which is the best name for the CSS/SCSS files. 6 | 7 | ## 1. The underscore character 8 | 9 | The first difference that we have to highlight is about the `_` underscore character. It is used at the beginning of the name of those files that don't need to be compiled. 10 | 11 | Most of the SCSS files must have the `_` underscore character, the only exceptions should be that particular stylesheet that needs to be imported into a different module or part of the application and the main.SCSS. 12 | 13 | --- 14 | 15 | **SCSS Input** 16 | 17 | - src 18 | - text-editor 19 | - text_editor.scss 20 | - blog 21 | - \_blog.scss 22 | - main.scss 23 | ```scss 24 | @import blog/_blog.scss; 25 | @import text-editor/text_editor.scss; 26 | ``` 27 | 28 | **CSS Output** 29 | 30 | - build 31 | - text_editor.css 32 | ```scss 33 | // this file contains the text_editor.scss style 34 | ``` 35 | - main.css 36 | ```scss 37 | // this file contains the _blog.scss + text_editor.scss style 38 | ``` 39 | 40 | In this example the code is generating 2 different CSS and it would be perfect if we had 2 different modules. 41 | 42 | **HTML Use** 43 | 44 | - module_main.html 45 | ```html 46 | 47 | ``` 48 | - module_text_editor.html 49 | ```html 50 | 51 | ``` 52 | 53 | --- 54 | 55 | But, If you don't need the second generated CSS, please use the `_` underscore character to avoid the creation of an extra compiled files. 56 | 57 | **SCSS Input** 58 | 59 | - src 60 | - text-editor 61 | - `_`text_editor.scss 62 | - blog 63 | - \_blog.scss 64 | - main.scss 65 | ```scss 66 | @import blog/_blog.scss; 67 | @import text-editor/_text_editor.scss; 68 | ``` 69 | 70 | **CSS Output** 71 | 72 | - build 73 | - main.scss 74 | ```scss 75 | // this file contains the _blog.scss + _text_editor.scss styles 76 | ``` 77 | 78 | **HTML Use** 79 | 80 | - module_main.html 81 | ```html 82 | 83 | ``` 84 | 85 | ## 2. User friendly names 86 | 87 | Sometimes we need a composed name for our files but it can be a little confusing to decide for the best one, so we defined a few simple rules: 88 | 89 | - the name must use lowercase Unicode characters 90 | - use the `_` underscore character at the beginning of the name as mentioned in the previous section 91 | - if the file goes in `Portal` use the `_` underscore character between the words 92 | - if the file goes in `Clay` use a single `-` hyphen character between the words 93 | 94 | So, don't use: 95 | 96 | - \_textEditor.scss 97 | - \_text.editor.scss 98 | - \_Text-Editor.scss 99 | 100 | But, use: 101 | 102 | - \_text`_`editor.scss in `Portal` 103 | - \_text`-`editor.scss in `Clay` 104 | 105 | Sometimes the name could be more complex: 106 | 107 | > We need a CSS file for the button of the card component of the sidebar template of the theme Fjord 108 | 109 | Generally we have directories that create the right separation and we don't need to worry about it. 110 | 111 | > theme>template>component>part 112 | 113 | - fjord 114 | - sidebar 115 | - card 116 | - \_button.scss 117 | 118 | In the case we don't have access to them and since we are working in `Portal`, the right name is: 119 | 120 | - \_fjord_sidebar_card_button.scss 121 | -------------------------------------------------------------------------------- /general/comments.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/general/comments.md). 2 | 3 | # Comments 4 | 5 | ## JavaScript 6 | 7 | ### Terminology 8 | 9 | There are two types of comments in JavaScript ([called "multi-line comments" and "single line-comments" in the spec](https://tc39.es/ecma262/#sec-comments)): 10 | 11 | ```javascript 12 | /* 13 | * This is multi-line comment syntax. 14 | */ 15 | 16 | // This is single-line comment syntax. 17 | ``` 18 | 19 | Despite their names, it is possible to make a "multi-line" comment that occupies only a single line, and multiple consecutive "single-line" comments can be used to make a message that spans multiple lines: 20 | 21 | ```javascript 22 | /* Still "multi-line" comment syntax, even though it's one line. */ 23 | 24 | // And this is still single-line comment 25 | // syntax even though it spans multiple lines. 26 | ``` 27 | 28 | A special variant of the multi-line syntax is [the "JSDoc-style" comment](https://jsdoc.app), which begins with `/**` and may contain [internal "tags"](https://jsdoc.app/about-block-inline-tags.html) beginning with `@`: 29 | 30 | ```javascript 31 | /** 32 | * This is a JSDoc-style comment. 33 | * 34 | * @see Something else. 35 | */ 36 | ``` 37 | 38 | ### Guidelines 39 | 40 | - Use JSDoc-style comments (ie. starting with `/**`) for comments that could conceivably be considered _documentation_ — eg. usage instructions, API descriptions etc — for a piece of code. Do this even in repos where we don't actually use any JSDoc tooling ([example](https://github.com/liferay/liferay-npm-tools/blob/764252718584a391f9340e8d3ed2db792dd938fa/packages/liferay-npm-scripts/src/format/substituteTags.js#L16-L22)), or when the "documentation" fits on a single line ([example](https://github.com/liferay/liferay-npm-tools/blob/764252718584a391f9340e8d3ed2db792dd938fa/packages/liferay-npm-scripts/src/format/Lexer.js#L51-L53)). 41 | - It is not required to add JSDoc type tags (eg. `@param` etc), but they may be useful when transitioning from JS to TypeScript because the TypeScript compiler can read them when the proper settings are configured. 42 | 43 | * For cases where you want to explain some _internal_ detail of the code (to help the reader understand how it works, but where such explanation wouldn't really belong in any extracted "documentation"), use single-line comment syntax (even for comments that go on for multiple lines). [Examples](https://github.com/liferay/liferay-npm-tools/blob/764252718584a391f9340e8d3ed2db792dd938fa/packages/liferay-npm-scripts/src/format/substituteTags.js#L35-L52). 44 | * Always leave a space after the `//`. 45 | * Start comments with a capital letter. 46 | * In general, end comments with a period, but note that you may wish to omit it if the comment is only one sentence and its role is a "header" or "title" of a section (this is a style rule used in many newspapers and media). 47 | * Use `//` in the trailing position of a line (ie. after code) sparingly, except for the rare (`// eslint-disable-line` case). 48 | * Gotchas: 49 | - Some ESLint directives only work with block comment syntax (eg. `eslint-disable`); while others (eg. `eslint-disable-line` and `eslint-disable-next-line`) work with block and line comments. 50 | 51 | ## Sass 52 | 53 | For comments in SCSS, follow the rules written in the [clay-css contributing documentation](https://github.com/liferay/clay/blob/master/packages/clay-css/CONTRIBUTING.md): 54 | 55 | > Comments should generally use the single line syntax, `// comment`, since single line comments are removed by the Sass preprocessor. 56 | > 57 | > Multiline comments, `/* comment */`, should only be used in places where we want to preserve the comment in the CSS output such as copyright text or attribution. 58 | 59 | ## See also 60 | 61 | - [Original discussion of commenting guidelines](https://github.com/liferay/liferay-frontend-guidelines/issues/96) ([\#96](https://github.com/liferay/liferay-frontend-guidelines/issues/96)). 62 | -------------------------------------------------------------------------------- /general/security.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/general/security.md). 2 | 3 | # Security 4 | 5 | ## Dependabot 6 | 7 | [Dependabot](https://dependabot.com/) is a [GitHub-owned](https://dependabot.com/blog/hello-github/) bot that sends pull requests (such as [this example PR](https://github.com/liferay/liferay-frontend-guidelines/pull/126)) updating dependencies containing potential vulnerabilities to newer versions. 8 | 9 | > Our general policy on these pulls is **not to merge** them but rather take them as a cue that we should review the dependency and perform a manual update at an appropriate time, if required. 10 | 11 | Rationale: 12 | 13 | - Lockfile-only changes are opaque (hard to understand) and brittle (easily reverted, perhaps by accident, when running package manager commands). The place to make an explicit _declaration_ of what versions of dependencies you wish to use in a project is the `package.json` file. 14 | - Lockfile-only changes provide a false sense of security because they affect only the local development environment of people working in the repository: the actual consumers of the NPM packages install dependencies based on the version ranges declared in the `package.json`, combined with the dependency resolution logic implemented by their package manager (`npm`, `yarn` etc); the lockfile is not included in the package itself. The outcome of the dependency resolution algorithm is unpredictable because it depends on which package manager is used (and which version!), and is time-sensitive (in the sense that it depends on the mutable contents of the package registry at the time of execution). 15 | - The way to truly force the outcome of dependency resolution, in the event that that can't be achieved via version ranges in `package.json` is to use a `resolutions` field (often, this is required by transitive dependencies, especially if there are multiple conflicting or ambiguous requirements for a given package), but note that even this isn't ideal because it depends on the use of a specific package manager (Yarn), and really only works at the application level (ie. the consumer of a package) and not at the library level. As such, the preferred method of moving to a newer version of a transitive dependency is to update the other dependencies responsible for bringing it in in the first place. This may entail a delay, but see the next point for why updates may not be so urgent. 16 | - The impact of "security vulnerabilities" can only be determined with careful evaluation. For example, the "vulnerable" package in question might pose no risk at all if it is only installed in the development environment and is never exposed to any untrusted input. In the example PR linked to above, for instance, `yarn why` shows that [acorn](https://www.npmjs.com/package/acorn) is being used as a transitive dependency of [ESLint](https://www.npmjs.com/package/eslint), which is only used in development. A malicious PR seeking to exploit this vulnerability would, at most, run in the sandboxed environment of [Travis CI](https://travis-ci.org/github/liferay) or [GitHub Actions](https://github.com/features/actions). 17 | - In addition to requiring manual review in order to assess actual security impact, manual review is also required in order to evaluate the risk of regressions. Automated updates are only safe if affected packages in the dependency graph actually employ [SemVer](https://semver.org/) correctly, but that can't be guaranteed unless we can exclude the possibility of human error, which we obviously can't. 18 | - The need for (potentially costly) human review means that the only efficient way to deal with these updates is to batch them; the cost of manually reviewing and QA-ing pull requests as they arrive is too high. (Note: this is not an argument for totally _ignoring_ the Dependabot pull requests; we still need to do some immediate triage on these things as they arrive to determine whether or not there is an actually critical vulnerability at play which would necessitate an immediate update and release.) 19 | -------------------------------------------------------------------------------- /dxp/exporting.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/dxp/exporting.md). 2 | 3 | # Exporting your JavaScript 4 | 5 | This document describes how you should organize your OSGi modules to export JavaScript functionality. 6 | 7 | ## Introduction 8 | 9 | Although you usually consume packages via [npm](https://npmjs.com), you are very likely to create packages yourself: for example, if you want to expose useful functionality and be able to reuse it elsewhere. 10 | 11 | In DXP, although we don't create npm packages directly and [advise not creating packages](https://github.com/liferay/liferay-frontend-guidelines/blob/ba259d1ed591a70d8d62932591f5ad6f5c7da99a/general/creating_a_new_npm_package.md), we do make use of their features: by adding a `package.json` to our OSGi modules, we are able to share code across modules. These modules are never published to npm. 12 | 13 | ## Declaring your module 14 | 15 | All the information related to your module, lives in a `package.json` file: as its name indicates, this file is written in JSON and needs to be valid. 16 | 17 | If you don't see a `package.json` file in the root of your OSGi module, you can create one. 18 | 19 | Here's an example: 20 | 21 | ```json 22 | { 23 | "main": "index.js", 24 | "name": "mymodule", 25 | "scripts": { 26 | "build": "liferay-npm-scripts build", 27 | "checkFormat": "liferay-npm-scripts check", 28 | "format": "liferay-npm-scripts fix" 29 | }, 30 | "version": "1.0.0" 31 | } 32 | ``` 33 | 34 | Make sure to change the `name` so that it matches the name of the OSGi module and the `version` which needs to correspond to the version of the (OSGi) module's `bnd.bnd` file. 35 | 36 | As an example, you can look at an existing [`package.json`](https://github.com/liferay/liferay-portal/blob/b4c82067fd9450bf1574d98335afa00f65172cf5/modules/apps/frontend-js/frontend-js-web/package.json) and [`bnd.bnd`](https://github.com/liferay/liferay-portal/blob/b4c82067fd9450bf1574d98335afa00f65172cf5/modules/apps/frontend-js/frontend-js-web/bnd.bnd) file. 37 | 38 | ### Using the `main` field 39 | 40 | Make sure the `package.json` file contains a `main` field. 41 | 42 | This is the [primary](https://docs.npmjs.com/files/package.json#main) entry point of your JavaScript module, and is where you'll define your "public" API. 43 | 44 | You want the value of the `main` field to be `index.js` 45 | 46 | Here are a few examples of existing modules in DXP: 47 | 48 | - [`frontend-editor-ckeditor-web`](https://github.com/liferay/liferay-portal/blob/b4c82067fd9450bf1574d98335afa00f65172cf5/modules/apps/frontend-editor/frontend-editor-ckeditor-web/package.json#L8) 49 | - [`frontend-js-web`](https://github.com/liferay/liferay-portal/blob/b4c82067fd9450bf1574d98335afa00f65172cf5/modules/apps/frontend-js/frontend-js-web/package.json#L35) 50 | - [`frontend-js-react-web`](https://github.com/liferay/liferay-portal/blob/b4c82067fd9450bf1574d98335afa00f65172cf5/modules/apps/frontend-js/frontend-js-react-web/package.json#L16) 51 | 52 | Note that you must use `index.js` and not `index.es.js` (as explained [here](https://github.com/liferay/liferay-frontend-guidelines/blob/ba259d1ed591a70d8d62932591f5ad6f5c7da99a/general/file_names.md), those files are there for historical reasons). 53 | 54 | The file you indicate in the `main` field needs to exist, and even though it's not obvious it needs to be located in the `src/main/resources/META-INF/resources` directory of your module. 55 | 56 | ### What to export 57 | 58 | What you export is totally up to you, but be warned that once you start exporting something, you'll most likely never be able to remove it: we want to maintain backward compatibility and avoid breaking changes at all cost. 59 | 60 | Another important thing to have in mind, is that you don't have to export everything that's in your package, what you export actually defines what the "outer world" can see and use, so don't export something if there's no need for it (for example: functions that are only called in your package). 61 | 62 | Make sure to choose the correct name when exporting, because once it's exported, it's visible and there are chances that it will be used. 63 | -------------------------------------------------------------------------------- /general/commit_messages.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/general/commit_messages.md). 2 | 3 | # Commit message format 4 | 5 | > **NOTE:** Although this guidance is framed in terms of commit messages, it applies equally to Pull Request titles and messages. 6 | 7 | This repo follows the "[Conventional Commits](https://www.conventionalcommits.org/)" specification, and we should also apply it in our other GitHub repos (with the exception of [liferay-portal](https://github.com/liferay/liferay-portal)). The specification provides consistent structure and metadata for our commits. If we additionally follow the same patterns for our Pull Requests, we can accurately generate accurate and informative release notes as well, using a tool like [liferay-changelog-generator](https://github.com/liferay/liferay-npm-tools/tree/master/packages/liferay-changelog-generator), which bases changelog on GitHub Pull Request titles extracted from merge commits. 8 | 9 | In this repo (and others), we use the [Semantic Pull Request](https://github.com/probot/semantic-pull-requests) bot to check for deviations from the format (see the [bot configuration](https://github.com/liferay/liferay-frontend-guidelines/blob/master/.github/semantic.yml), and a [sample bad PR](https://github.com/liferay/liferay-frontend-guidelines/pull/71)). 10 | 11 | ## Message format 12 | 13 | Each message consists of a title and optional body and footer. The title has a special format that includes a type, an optional scope and a description of the change: 14 | 15 | ``` 16 | type(optional scope): description 17 | 18 | optional body 19 | 20 | optional footer 21 | ``` 22 | 23 | ### Type 24 | 25 | The Conventional Commits spec defines the following types: 26 | 27 | - **feat**: A new feature. 28 | - **fix**: A bug fix. 29 | 30 | Frequently used types that are not in the specification but which are widely used (for example, in [the `@commitlint/config-conventional`](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional) and [the Angular conventions](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines)) include: 31 | 32 | - **chore**: Changes that deliver value despite not delivering features or fixing bugs (eg. dependency upgrades, preparing releases etc). 33 | - **docs**: Documentation-only changes. 34 | - **perf**: Performance-related changes. 35 | - **refactor**: A code change that does not change behavior. 36 | - **style**: Formatting changes. 37 | - **test**: New or updated tests. 38 | 39 | ### Scope 40 | 41 | The scope could be anything specifying place of the commit change. For example `feat(@clayui/dropdown)`, `feat(@clayui/css)`, `fix(next.clayui.com)`, `docs(Badge)`, `fix(useCache)`, etc... 42 | 43 | ### Description 44 | 45 | The description should start with a verb: 46 | 47 | - fix: **stop** button bar from jumping on IE 11 48 | - refactor: **simplify** options parser 49 | - chore: **prepare** v2.3 release 50 | 51 | and may, if it is in relation to an issue, include the issue number in parentheses as a suffix: 52 | 53 | - fix: make archive generation idempotent **(#12)** 54 | - test: fill in holes in picker test coverage **(#92)** 55 | 56 | Endeavor to make the description as concise as possible while still communicating what the change does. It is desirable to keep the total title length under 72 characters if possible (some places recommend it be as short as 50 characters), but this is a guideline rather than a hard rule. Do your best and use your judgment. 57 | 58 | ### Footer 59 | 60 | The footer may contain links or other metadata such as related issues. On GitHub specifically, a "Closes:" line can be used to automatically close an issue: 61 | 62 | ``` 63 | Closes: https://github.com/liferay/liferay-frontend-guidelines/issues/9 64 | ``` 65 | 66 | ### Breaking changes 67 | 68 | Breaking changes should include a "BREAKING CHANGE:" line at the beginning of the body or the footer: 69 | 70 | ``` 71 | BREAKING CHANGE: The x() function now returns a promise. 72 | ``` 73 | 74 | Additionally, you may highlight the presence of the breaking change by including an exclamation mark immediately before the colon on the commit title as in the following examples: 75 | 76 | - feat!: expose new version of table API (#12) 77 | - fix(bundler)**!**: replace invalid output format (#101) 78 | -------------------------------------------------------------------------------- /general/naming.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/general/naming.md). 2 | 3 | # Naming guidelines 4 | 5 | > There are only two hard things in Computer Science: cache invalidation and naming things. 6 | 7 | ([Source](https://skeptics.stackexchange.com/a/39178)) 8 | 9 | ## General naming advice 10 | 11 | ### Avoid abbreviations 12 | 13 | Using abbreviations can make code harder to understand, especially on an international team where English may not be everybody's first language. 14 | 15 | Thanks to tools like [Prettier](https://prettier.io/) that can wrap code neatly and readably, we don't have to worry so much about minimizing line width. We can instead prioritize encoding information clearly, accurately, and unambiguously. 16 | 17 | #### Examples of _bad_ abbreviations: 18 | 19 | | Abbreviations | Preferred alternatives | 20 | | ------------------ | ------------------------------------------- | 21 | | :x: `arr` | :white_check_mark: `array` | 22 | | :x: `btn` | :white_check_mark: `button` | 23 | | :x: `cb` | :white_check_mark: `callback` | 24 | | :x: `desc` | :white_check_mark: `description` | 25 | | :x: `e`/`err` | :white_check_mark: `error` | 26 | | :x: `el` | :white_check_mark: `element` | 27 | | :x: `evt` | :white_check_mark: `event` | 28 | | :x: `fm` | :white_check_mark: `form` | 29 | | :x: `fmt` | :white_check_mark: `format` | 30 | | :x: `k`/`v` | :white_check_mark: `key`/`value` | 31 | | :x: `idx` | :white_check_mark: `index` | 32 | | :x: `img` | :white_check_mark: `image` | 33 | | :x: `obj` | :white_check_mark: `object` | 34 | | :x: `opts` | :white_check_mark: `options` | 35 | | :x: `prj` | :white_check_mark: `project` | 36 | | :x: `sm`/`md`/`lg` | :white_check_mark: `small`/`medium`/`large` | 37 | 38 | #### Examples of _good_ abbreviations: 39 | 40 | - `i`, `j`, `k` (when used as loop variables): 41 | 42 | ```js 43 | for (let i = 0; i < students.length; i++) { 44 | students[i].enroll(); 45 | } 46 | ``` 47 | 48 | **Note:** The question of when to use `for` vs alternatives like `Array.prototype.forEach` is a separate topic. 49 | 50 | **Note:** Even though `i`, `j`, `k` are acceptable idiomatic variable names for loops, this doesn't mean that you _have_ to use them; if there is a more descriptive unabbreviated name, feel free to use it: 51 | 52 | ```js 53 | while (remainingTries > 0) { 54 | reserveToken(); 55 | 56 | remainingTries--; 57 | } 58 | ``` 59 | 60 | - `a`, `b` (when used as generic values): 61 | 62 | ```js 63 | list.sort((a, b) => { 64 | if (a > b) { 65 | return 1; 66 | } else if (a < b) { 67 | return -1; 68 | } else { 69 | return 0; 70 | } 71 | }); 72 | ``` 73 | 74 | - Widely-used acronyms: 75 | 76 | ```js 77 | // Not `sampleHypetextMarkupLanguage`: 78 | const sampleHTML = '

Your markup here...

'; 79 | 80 | // Not `homeUniformResourceLocator`: 81 | const homeURL = 'http://example.net'; 82 | ``` 83 | 84 | #### Examples of "_it depends_" abbreviations: 85 | 86 | - `x`, `y`: 87 | 88 | In this case, `x` and `y` are obviously _coordinates_ in a Canvas, so the abbreviation is **good:** 89 | 90 | ```js 91 | for (let x = left; x < right; x++) { 92 | for (let y = top; y < bottom; y++) { 93 | canvas.fillStyle = getRandomStyle(); 94 | canvas.fillRect(x, y, x + 1, y + 1); 95 | } 96 | } 97 | ``` 98 | 99 | Here `x` and `y` are _algebraic_, so the abbreviation is **good**: 100 | 101 | ```js 102 | function add(x, y) { 103 | return x + y; 104 | } 105 | ``` 106 | 107 | In this counterexample, more descriptive alternative names exist, so the abbreviation is **bad:** 108 | 109 | ```js 110 | // `x` and `y` are strings; better names would be `string` and `prefix`: 111 | function hasPrefix(x, y) { 112 | return x.startsWith(y); 113 | } 114 | ``` 115 | -------------------------------------------------------------------------------- /general/testing/ui_components.md: -------------------------------------------------------------------------------- 1 | > :warning: The contents of this repo have been migrated [to the `liferay/liferay-frontend-projects` monorepo](https://github.com/liferay/liferay-frontend-projects) and more specifically to the [to the `guidelines/` directory](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines). Maintenance will continue there, and this repo will be archived (ie. switched to read-only mode). Depending on whether files have moved in the new repo, you may be able to see the current version of this page at [this location](https://github.com/liferay/liferay-frontend-projects/tree/master/guidelines/general/testing/ui_components.md). 2 | 3 | # Testing UI components 4 | 5 | ## High-level principles 6 | 7 | ### On shapshots 8 | 9 | > It's nice to have a few general snapshots of how a component renders, but ... the more snapshots you have, the less helpful they are. 10 | > 11 | > — [@bryceosterhaus](https://github.com/liferay/liferay-frontend-guidelines/issues/49#issuecomment-506774669) 12 | 13 | Snapshots SHOULD only be used as "smoke tests": they are very good for verifying that a component renders at all, but they are _not_ good for communicating intent or what is important. For example, when something in a large snapshot changes, there is nothing intrinsic in the snapshot to communicate whether any particular part of it is important. 14 | 15 | This doesn't mean that snapshots are bad — a snapshot test is better than no tests at all — but that they should be used sparingly, and complemented with more-targeted assertions. 16 | 17 | ### On `data-testid` attributes 18 | 19 | - Shipping test attributes to the client is overtly bad because it increases network payloads, parse times, DOM bloat etc, and ends up becoming an unofficial API. 20 | - Therefore, we MUST NOT send "data-testid" to the client. 21 | - The code and markup that is exercised in the test environment should match that which is used in the production environment, otherwise you are not actually testing the thing that you would like to test. 22 | - Because of the recommendation above, any usage of "data-testid" MUST be stripped from when outside the test environment. 23 | - But because of the desire to not have divergent code paths, "data-testid" SHOULD NOT be used, except as an interim measure. 24 | 25 | > A "data-testid" says "this thing is important to me". But if it is important, why isn't it targetable in some user-visible way? That is, users care about behavior and content of specific things (buttons, sliders, charts etc). If the only way I can label something as important is via _a mechanism that is invisible to the user_ (not even in the source code on the client), then there is a risk that I might be testing an implementation detail and not something which a user actually cares about. 26 | 27 | #### Targeting elements without `data-testid` 28 | 29 | - In the absence of "data-testid", we seek the most stable way to target elements in tests ("stable" in the sense that tests are less likely to break as a result of superficial changes); we think the methods that are most likely to remain stable are the following and therefore recommend that we SHOULD use: 30 | - Attributes or content that are significant to the user (for example, the text of a button such as "Publish"); at the end of the day, the visible (or audible) content and behavior of a product is literally the reason we build products, so the highest value tests should focus on aspects that are perceptible and observable to the user. In the button example, if the text changes, then we _want_ the test to break. 31 | - `id` attributes when a semantically meaningful and unique "identity" can be assigned to an element (obvious considerations about multiple instances in a document needing to be unique apply). 32 | - `class` based selectors where the name is semantically meaningful (ie. describing the role of the element as opposed to controlling the presentational aspects of it). 33 | - other selectors, with a strong preference for _semantic_ elements because these are less likely to change over time (that is a list `
    ` is likely to remain a list, and a `