Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: webpack support #11346

Merged
merged 2 commits into from Aug 10, 2020
Merged

feat: webpack support #11346

merged 2 commits into from Aug 10, 2020

Conversation

janvennemann
Copy link
Contributor

@janvennemann janvennemann commented Nov 19, 2019

JIRA: https://jira.appcelerator.org/browse/TIMOB-27429

This adds a new CLI hook that will allow the use of Webpack with Titanium.

Motivation

The current JavaScript processing pipeline in Titanium is mainly done in node-titanium-sdk. Although we improved the implementation a lot in the last couple major releases, it still lacks the ability to be customized by users. The only configuration that can be done is to include additional Babel plugins.

With our continuing effort to improve compatibility with Node.js core modules the use of third-party Node modules should also be simplified. Depending on the project type, currently they have to be installed into Resources/node_modules (Classic) or app/lib/node_modules (Alloy). This is not very intuitive, since users are used to installing Node modules directly into the project root.

Last but not least, to support Angular and Vue.js implementations a Webpack build pipeline needs to be in place. The tooling around those is depending on Webpack.

Features

To address the issues outlined above an alternative JavaScript build pipeline using Webpack is now in place. It comes with pre-configured configurations for existing Classic and Alloy projects, as well as for the upcoming Vue.js and Angular implementations (experimental). Webpack support is implemented as a plugin for the Appc Daemon.

Features for the initial release:

  • Bundle source code and assets of apps using Webpack
  • Pre-defined Webpack configurations for all Titanium projects. These should work out of the box for most projects but are fully configurable using hooks/plugins if need be.
  • Webpack builds are performed in a background process and automatically re-build changed files to minimize incremental app build times.
  • Easy use of Node modules. Install NPM packages directly into the project root directory and require them in Titanium code. Webpack then takes care of the rest and makes sure to properly resolve and bundle them into the app.
  • Web UI to view build stats and control the Webpack builds.

Migration

There is a detailed migration guide for existing projects. It describes all steps required to enable Webpack in existing projects and what changes are required for the project. Changes to the code base should be minimal and only affect require/import statements in most cases.

How it works

The hook communicates with the Appc Daemon to start new Weback builds and query the current status of running builds. The majority of the work is then done by the daemon in the background. appcd-plugin-webpack will automatically configure Webpack based on the project type so users don't have to deal with Webpack configuration files.

Webpack uses Resources as its output directory so our build can continue as usual once Webpack is done. This ensures minimal changes to the existing build pipeline.

To get the most out of the "always running" approach of the daemon, once Webpack is started it keeps running for as long as you actively work on a project. After 10min of no activity it stops to save system resources. On the the next build it will automatically be started again. It is also possible to start and stop the Webpack build via the Web UI.

The Webpack plugin will make sure to restart the build when necessary, for example when

  • Build settings change, like target or deploy type.
  • Files change, like tiapp.xml or babel.config.js
  • The --force flag is explicitly set

As a result, there are two kinds of clean builds when Webpack is enabled. The first one when Webpack is not started, which is basically the same as usual. Everything starts from scratch, including JS processing through Webpack. And all builds from then on will just query the daemon for the current Webpack build status. If everything is up-to-date the whole JS handling in our build will be skipped. This means that even subsequent clean builds are faster.

Benchmarks

I currently only have benchmarks for Alloy apps since i don't have a decently large Classic project to test with. Anyway, let's talk some numbers. You will notice that there are two passes for clean builds. The first one is with Webpack not running, the second one with Webpack already running. All times are the average of three builds.

Timings with our very own KitchenSink v2:

Task Standard Webpack
Clean build #1 32s 36s
Clean build #2 32s 30s
Incremental build 8s 789ms

The first clean build where Webpack is not already running takes a little longer since there is the overhead of starting Webpack, which can take a couple of seconds.

Now, the bigger the app gets, the better the build time improvements become. The following benchmarks were done with a large real-world project that contains ~100 controllers/views, ~20 widgets and NPM dependencies.

Task Standard Webpack
Clean build #1 2m 6s 1m 55s
Clean build #2 2m 6s 1m 36s
Incremental build 20s 965ms 1s 861ms

Action items

  • The hooks tries to locate the appcd daemon either globally when run via ti build or the shipped version included in appc-cli when run with appc run. Depending on whether the appc-cli daemon can be updated to v3.1.0 or not this behavior needs to be adjusted. appc-cli@8.0.0 ships with appcd@3.1.0!
  • Evaluate Webpack generated source maps.
  • Update node-titanium-sdk once feat: add webpack section node-titanium-sdk#128 is merged.
  • Create new project templates that have Webpack enabled by default.

Testing Instructions

I've created a new branch of our Kitchensink App which can be used for easy testing: https://github.com/janvennemann/kitchensink-v2/tree/webpack

To enable Webpack in any other project follow the migration guide.

Kown issues

  • Webpack builds are not yet compatible with Hyperloop. All Hyperoop related requires need to be marked as external so Webpack doesn't try to bundle them

@build
Copy link
Contributor

build commented Nov 19, 2019

Warnings
⚠️ This PR has milestone set to 9.1.0, but the version defined in package.json is 9.2.0 Please either: - Update the milestone on the PR - Update the version in package.json - Hold the PR to be merged later after a release and version bump on this branch
Messages
📖

💾 Here's the generated SDK zipfile.

📖 ✊ The commits in this PR match our conventions! Feel free to Rebase and Merge this PR when ready.
📖

✅ All tests are passing
Nice one! All 7277 tests are passing.
(There are 708 skipped tests not included in that total)

New dependencies added: ansi-escapes, boxen and wrap-ansi.

ansi-escapes

Author: Sindre Sorhus

Description: ANSI escape codes for manipulating the terminal

Homepage: https://github.com/sindresorhus/ansi-escapes#readme

Createdalmost 5 years ago
Last Updated5 months ago
LicenseMIT
Maintainers1
Releases17
Direct Dependenciestype-fest
Keywordsansi, terminal, console, cli, string, tty, escape, escapes, formatting, shell, xterm, log, logging, command-line, text, vt100, sequence, control, code, codes, cursor, iterm and iterm2
README

ansi-escapes Build Status

ANSI escape codes for manipulating the terminal

Install

$ npm install ansi-escapes

Usage

const ansiEscapes = require('ansi-escapes');

// Moves the cursor two rows up and to the left
process.stdout.write(ansiEscapes.cursorUp(2) + ansiEscapes.cursorLeft);
//=> '\u001B[2A\u001B[1000D'

API

cursorTo(x, y?)

Set the absolute position of the cursor. x0 y0 is the top left of the screen.

cursorMove(x, y?)

Set the position of the cursor relative to its current position.

cursorUp(count)

Move cursor up a specific amount of rows. Default is 1.

cursorDown(count)

Move cursor down a specific amount of rows. Default is 1.

cursorForward(count)

Move cursor forward a specific amount of columns. Default is 1.

cursorBackward(count)

Move cursor backward a specific amount of columns. Default is 1.

cursorLeft

Move cursor to the left side.

cursorSavePosition

Save cursor position.

cursorRestorePosition

Restore saved cursor position.

cursorGetPosition

Get cursor position.

cursorNextLine

Move cursor to the next line.

cursorPrevLine

Move cursor to the previous line.

cursorHide

Hide cursor.

cursorShow

Show cursor.

eraseLines(count)

Erase from the current cursor position up the specified amount of rows.

eraseEndLine

Erase from the current cursor position to the end of the current line.

eraseStartLine

Erase from the current cursor position to the start of the current line.

eraseLine

Erase the entire current line.

eraseDown

Erase the screen from the current line down to the bottom of the screen.

eraseUp

Erase the screen from the current line up to the top of the screen.

eraseScreen

Erase the screen and move the cursor the top left position.

scrollUp

Scroll display up one line.

scrollDown

Scroll display down one line.

clearScreen

Clear the terminal screen. (Viewport)

clearTerminal

Clear the whole terminal, including scrollback buffer. (Not just the visible part of it)

beep

Output a beeping sound.

link(text, url)

Create a clickable link.

Supported terminals. Use supports-hyperlinks to detect link support.

image(filePath, options?)

Display an image.

Currently only supported on iTerm2 >=3

See term-img for a higher-level module.

input

Type: Buffer

Buffer of an image. Usually read in with fs.readFile().

options

Type: object

width
height

Type: string | number

The width and height are given as a number followed by a unit, or the word "auto".

  • N: N character cells.
  • Npx: N pixels.
  • N%: N percent of the session's width or height.
  • auto: The image's inherent size will be used to determine an appropriate dimension.
preserveAspectRatio

Type: boolean
Default: true

iTerm.setCwd(path?)

Type: string
Default: process.cwd()

Inform iTerm2 of the current directory to help semantic history and enable Cmd-clicking relative paths.

iTerm.annotation(message, options?)

Creates an escape code to display an "annotation" in iTerm2.

An annotation looks like this when shown:

See the iTerm Proprietary Escape Codes documentation for more information.

message

Type: string

The message to display within the annotation.

The | character is disallowed and will be stripped.

options

Type: object

length

Type: number
Default: The remainder of the line

Nonzero number of columns to annotate.

x

Type: number
Default: Cursor position

Starting X coordinate.

Must be used with y and length.

y

Type: number
Default: Cursor position

Starting Y coordinate.

Must be used with x and length.

isHidden

Type: boolean
Default: false

Create a "hidden" annotation.

Annotations created this way can be shown using the "Show Annotations" iTerm command.

Related

  • ansi-styles - ANSI escape codes for styling strings in the terminal

Get professional support for this package with a Tidelift subscription
Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies.

boxen

Author: Sindre Sorhus

Description: Create boxes in the terminal

Homepage: https://github.com/sindresorhus/boxen#readme

Createdover 4 years ago
Last Updated8 months ago
LicenseMIT
Maintainers1
Releases25
Direct Dependenciesansi-align, camelcase, chalk, cli-boxes, string-width, term-size, type-fest and widest-line
Keywordscli, box, boxes, terminal, term, console, ascii, unicode, border and text
README

boxen Build Status

Create boxes in the terminal

Install

$ npm install boxen

Usage

const boxen = require('boxen');

console.log(boxen('unicorn', {padding: 1}));
/*
┌─────────────┐
│             │
│   unicorn   │
│             │
└─────────────┘
*/

console.log(boxen('unicorn', {padding: 1, margin: 1, borderStyle: 'double'}));
/*

   ╔═════════════╗
   ║             ║
   ║   unicorn   ║
   ║             ║
   ╚═════════════╝

*/

API

boxen(text, options?)

text

Type: string

Text inside the box.

options

Type: object

borderColor

Type: string
Values: 'black' 'red' 'green' 'yellow' 'blue' 'magenta' 'cyan' 'white' 'gray' or a hex value like '#ff0000'

Color of the box border.

borderStyle

Type: string | object
Default: 'single'
Values:

  • 'single'
┌───┐
│foo│
└───┘
  • 'double'
╔═══╗
║foo║
╚═══╝
  • 'round' ('single' sides with round corners)
╭───╮
│foo│
╰───╯
  • 'bold'
┏━━━┓
┃foo┃
┗━━━┛
  • 'singleDouble' ('single' on top and bottom, 'double' on right and left)
╓───╖
║foo║
╙───╜
  • 'doubleSingle' ('double' on top and bottom, 'single' on right and left)
╒═══╕
│foo│
╘═══╛
  • 'classic'
+---+
|foo|
+---+

Style of the box border.

Can be any of the above predefined styles or an object with the following keys:

{
	topLeft: '+',
	topRight: '+',
	bottomLeft: '+',
	bottomRight: '+',
	horizontal: '-',
	vertical: '|'
}
dimBorder

Type: boolean
Default: false

Reduce opacity of the border.

padding

Type: number | object
Default: 0

Space between the text and box border.

Accepts a number or an object with any of the top, right, bottom, left properties. When a number is specified, the left/right padding is 3 times the top/bottom to make it look nice.

margin

Type: number | object
Default: 0

Space around the box.

Accepts a number or an object with any of the top, right, bottom, left properties. When a number is specified, the left/right margin is 3 times the top/bottom to make it look nice.

float

Type: string
Default: 'left'
Values: 'right' 'center' 'left'

Float the box on the available terminal screen space.

backgroundColor

Type: string
Values: 'black' 'red' 'green' 'yellow' 'blue' 'magenta' 'cyan' 'white' 'gray' or a hex value like '#ff0000'

Color of the background.

align

Type: string
Default: 'left'
Values: 'left' 'center' 'right'

Align the text in the box based on the widest line.

Related

  • boxen-cli - CLI for this module
  • cli-boxes - Boxes for use in the terminal
  • ink-box - Box component for Ink that uses this package

Get professional support for this package with a Tidelift subscription
Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies.

wrap-ansi

Author: Sindre Sorhus

Description: Wordwrap a string with ANSI escape codes

Homepage: https://github.com/chalk/wrap-ansi#readme

Createdalmost 5 years ago
Last Updated4 months ago
LicenseMIT
Maintainers3
Releases16
Direct Dependenciesansi-styles, string-width and strip-ansi
Keywordswrap, break, wordwrap, wordbreak, linewrap, ansi, styles, color, colour, colors, terminal, console, cli, string, tty, escape, formatting, rgb, 256, shell, xterm, log, logging, command-line and text
README

wrap-ansi Build Status Coverage Status

Wordwrap a string with ANSI escape codes

Install

$ npm install wrap-ansi

Usage

const chalk = require('chalk');
const wrapAnsi = require('wrap-ansi');

const input = 'The quick brown ' + chalk.red('fox jumped over ') +
	'the lazy ' + chalk.green('dog and then ran away with the unicorn.');

console.log(wrapAnsi(input, 20));

API

wrapAnsi(string, columns, options?)

Wrap words to the specified column width.

string

Type: string

String with ANSI escape codes. Like one styled by chalk. Newline characters will be normalized to \n.

columns

Type: number

Number of columns to wrap the text to.

options

Type: object

hard

Type: boolean
Default: false

By default the wrap is soft, meaning long words may extend past the column width. Setting this to true will make it hard wrap at the column width.

wordWrap

Type: boolean
Default: true

By default, an attempt is made to split words at spaces, ensuring that they don't extend past the configured columns. If wordWrap is false, each column will instead be completely filled splitting words as necessary.

trim

Type: boolean
Default: true

Whitespace on all lines is removed by default. Set this option to false if you don't want to trim.

Related

  • slice-ansi - Slice a string with ANSI escape codes
  • cli-truncate - Truncate a string to a specific width in the terminal
  • chalk - Terminal string styling done right
  • jsesc - Generate ASCII-only output from Unicode strings. Useful for creating test fixtures.

Maintainers


Get professional support for this package with a Tidelift subscription
Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies.

Generated by 🚫 dangerJS against e831f0b

ensureAppcdVersion(appcdRootPath, MIN_APPCD_VERSION, true);
} else {
// plain ti-cli, load daemon from global modules
const globalModulesPath = execSync('npm root -g').toString().replace(/\s+$/, '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems potentially problematic on windows...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

execSync starts a new shell on windows and directly passes the command to that shell, so this shouldn't be an issue. I tested this on my Window 10 machine and works, but i'll ask QE to double check.

cli/hooks/webpack.js Outdated Show resolved Hide resolved
cli/hooks/webpack.js Outdated Show resolved Hide resolved
cli/lib/webpack/service.js Outdated Show resolved Hide resolved
cli/lib/webpack/service.js Outdated Show resolved Hide resolved
startWebpack(options) {
this.logger.debug(`Starting Webpack with options: ${JSON.stringify(options)}`);
this.webpackStatus = { state: 'building' };
return new Promise((resolve, reject) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really not a fan of all this hand-rolling of Promises, but I'm not sure there's a nice way to move to async/await with events like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, me neither, but i don't think there is a nice way around this since appcd-client is event based.

cc @cb1kenobi What do you think about having an alternative promise based API for simple requests like these? Always having to write request like the following can get quite repetitive.

client
  .request({
    path: '/appcd/status'
  })
  .once('response', onResponse)
  .once('error', onError);

I could imagine a promise based API with get(path): Promise and post(path, data): Promise, for example.

package.json Outdated Show resolved Hide resolved
cli/lib/webpack/service.js Outdated Show resolved Hide resolved
});
Promise.resolve().then(async () => {
await webpackService.build();
return callback();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ cli/hooks/webpack.js line 97 – Avoid calling back inside of a promise. (promise/no-callback-in-promise)

logger.error(e.stack);
}

callback(e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ cli/hooks/webpack.js line 115 – Avoid calling back inside of a promise. (promise/no-callback-in-promise)

@sgtcoolguy sgtcoolguy merged commit e5d8f15 into tidev:master Aug 10, 2020
sgtcoolguy pushed a commit that referenced this pull request Aug 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants