Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

React for Re-use: Creating UI Components with Confluence Connect

11,566 views

Published on

Using React to create reusable components for Confluence extension points saves time and allows for a richer user experience. Join Matt Jensen, an Atlassian developer for over 10 years, for a session on using React to modularise the UI layer of your Confluence add-on, then bringing it together to take advantage of the common components. Matt will demonstrate the benefits of React for UI elements like macros, pages, and dialogs.

Matthew Jensen, Development Team Lead, Atlassian

Published in: Software
  • Login to see the comments

React for Re-use: Creating UI Components with Confluence Connect

  1. 1. React for Reuse Creating reusable UI Components with React MATTHEW JENSEN | TEAM LEAD | ATLASSIAN | @MATTHEWJENSEN
  2. 2. Introduction About this Talk
  3. 3. Me Confluence Platform Ecosystem
  4. 4. Me Confluence Platform Ecosystem Confluence Platform
  5. 5. Me Confluence Platform Ecosystem Core APIs Confluence Platform Ecosystem Front End Platform
  6. 6. Me Confluence Platform Ecosystem Ecosystem Confluence Connect Add-ons Partnerships
  7. 7. Client Side We use extended JavaScript on the client side to create the UI Components. Server Side In this example we use server side JavaScript by using NodeJs. JavaScript Everywhere
  8. 8. Introduction Add-on Stack
  9. 9. Introduction Add-on Stack Express (ACE)Atlassian Connect Express
  10. 10. Introduction Add-on Stack Express (ACE)Atlassian Connect Express
  11. 11. Introduction Add-on Stack ExpressACE
  12. 12. Introduction Add-on Stack
  13. 13. Introduction Add-on Stack Static HTML
  14. 14. Introduction Add-on Stack
  15. 15. Sample Project Confluence Issues Add-on
  16. 16. bitbucket.org/mjensen/confluence-issues-addon bit.ly/2oqVGyb
  17. 17. Issues Add-On Overview Issue List Issue Macro
  18. 18. Issues Add-On Overview Issue List Issue Macro
  19. 19. Issues Add-On Overview Issue List Issue Macro
  20. 20. Issues Add-On Overview Issue List Issue Macro
  21. 21. Getting Started Setting up the Project
  22. 22. bitbucket.org/atlassian/atlassian-connect-express bit.ly/TTpDn1
  23. 23. Getting Started ACE Project Create the project Use atlas-connect to create a project using the confluence template.
  24. 24. confluence-issues-addon public routes views src
  25. 25. Components A React Primer
  26. 26. Components Functional Components are Functions. Classes State
  27. 27. Components function Clock(props) {
 return <span>{moment().format()}</span>;
 } ReactDOM.render(
 <Clock />,
 document.getElementById('root')
 ); Functional Classes State
  28. 28. Components function Clock(props) {
 return <span>{moment().format()}</span>;
 } ReactDOM.render(
 <Clock />,
 document.getElementById('root')
 ); Functional Classes State
  29. 29. Components function Clock(props) {
 return <span>{moment().format()}</span>;
 } ReactDOM.render(
 <Clock />,
 document.getElementById('root')
 ); Use a function to define a Component Functional Classes State
  30. 30. f(x) Functional Components Simple Defining a component as a function is very simple. Stateless Components defined as functions have no local state.
  31. 31. Components are Classes. Components Functional Classes State
  32. 32. class Clock extends React.Component {
 render() {
 return <span>{moment().format()}</span>;
 }
 } ReactDOM.render(
 <Clock />,
 document.getElementById('root')
 ); Components Functional Classes State
  33. 33. class Clock extends React.Component {
 render() {
 return <span>{moment().format()}</span>;
 }
 } ReactDOM.render(
 <Clock />,
 document.getElementById('root')
 ); Components Functional Classes State
  34. 34. class Clock extends React.Component {
 render() {
 return <span>{moment().format()}</span>;
 }
 } ReactDOM.render(
 <Clock />,
 document.getElementById('root')
 ); Components Functional Classes State Use a class to define a Component
  35. 35. Components as Classes Classes Classes can benefit from inheritance, composition and other object orientation strategies. Stateful Components defined as classes can define their own state and lifecycle.
  36. 36. Components have State. Components Functional Classes State
  37. 37. Components class Clock extends React.Component {
 constructor(props) {
 super(props);
 this.state = { date: moment() };
 } 
 render() {
 return ( <span> {this.state.date.format()} </span> );
 }
 } Functional Classes State
  38. 38. Components class Clock extends React.Component {
 constructor(props) {
 super(props);
 this.state = { date: moment() };
 } 
 render() {
 return ( <span> {this.state.date.format()} </span> );
 }
 } Functional Classes State
  39. 39. Components Functional Classes class Clock extends React.Component {
 constructor(props) {
 super(props);
 this.state = { date: moment() };
 } 
 render() {
 return ( <span> {this.state.date.format()} </span> );
 }
 } State Class components can have state.
  40. 40. State Management Component State React provides simple state management for each component. Centralised State Redux is a centralised state management system, often simplifying your components dramatically.
  41. 41. Components have a Lifecycle. Components Lifecycle Props
  42. 42. Components class Clock extends React.Component { // ... componentDidMount() {
 // called after the component is added to the DOM
 this.timerID = setInterval(() => this.tick(), 1000);
 }
 
 componentWillUnmount() {
 // called before the component is removed from the DOM
 clearInterval(this.timerID);
 }
 
 tick() {
 this.setState({ date: moment() }); } 
 // ...
 } Lifecycle Props
  43. 43. Components class Clock extends React.Component { // ... componentDidMount() {
 // called after the component is added to the DOM
 this.timerID = setInterval(() => this.tick(), 1000);
 }
 
 componentWillUnmount() {
 // called before the component is removed from the DOM
 clearInterval(this.timerID);
 }
 
 tick() {
 this.setState({ date: moment() }); } 
 // ...
 } Lifecycle Props
  44. 44. Components class Clock extends React.Component { // ... componentDidMount() {
 // called after the component is added to the DOM
 this.timerID = setInterval(() => this.tick(), 1000);
 }
 
 componentWillUnmount() {
 // called before the component is removed from the DOM
 clearInterval(this.timerID);
 }
 
 tick() {
 this.setState({ date: moment() }); } 
 // ...
 } Lifecycle Props
  45. 45. Components class Clock extends React.Component { // ... componentDidMount() {
 // called after the component is added to the DOM
 this.timerID = setInterval(() => this.tick(), 1000);
 }
 
 componentWillUnmount() {
 // called before the component is removed from the DOM
 clearInterval(this.timerID);
 }
 
 tick() {
 this.setState({ date: moment() }); } 
 // ...
 } Lifecycle Props The lifecycle methods update the state.
  46. 46. Props vs State. Components Lifecycle Props
  47. 47. Components // src/components/Clock.js class Clock extends React.Component { render() {
 return ( <span>{this.state.date.format(this.props.dateFormat)}</span> );
 }
 } Clock.defaultProps = {
 "dateFormat": 'MMMM Do YYYY, h:mm:ss'
 }; // clock.js ReactDOM.render(
 <Clock />,
 document.getElementById('clock')
 ); ReactDOM.render(
 <Clock dateFormat='h:mm:ss'/>,
 document.getElementById('clock')
 ); Lifecycle Props
  48. 48. // src/components/Clock.js class Clock extends React.Component { render() {
 return ( <span>{this.state.date.format(this.props.dateFormat)}</span> );
 }
 } Clock.defaultProps = {
 "dateFormat": 'MMMM Do YYYY, h:mm:ss'
 }; // clock.js ReactDOM.render(
 <Clock />,
 document.getElementById('clock')
 ); ReactDOM.render(
 <Clock dateFormat='h:mm:ss'/>,
 document.getElementById('clock')
 ); Components Lifecycle Props
  49. 49. // src/components/Clock.js class Clock extends React.Component { render() {
 return ( <span>{this.state.date.format(this.props.dateFormat)}</span> );
 }
 } Clock.defaultProps = {
 "dateFormat": 'MMMM Do YYYY, h:mm:ss'
 }; // clock.js ReactDOM.render(
 <Clock />,
 document.getElementById('clock')
 ); ReactDOM.render(
 <Clock dateFormat='h:mm:ss'/>,
 document.getElementById('clock')
 ); Components Lifecycle Props
  50. 50. // src/components/Clock.js class Clock extends React.Component { render() {
 return ( <span>{this.state.date.format(this.props.dateFormat)}</span> );
 }
 } Clock.defaultProps = {
 "dateFormat": 'MMMM Do YYYY, h:mm:ss'
 }; // clock.js ReactDOM.render(
 <Clock />,
 document.getElementById('clock')
 ); ReactDOM.render(
 <Clock dateFormat='h:mm:ss'/>,
 document.getElementById('clock')
 ); Components Lifecycle Props
  51. 51. // src/components/Clock.js class Clock extends React.Component { render() {
 return ( <span>{this.state.date.format(this.props.dateFormat)}</span> );
 }
 } Clock.defaultProps = {
 "dateFormat": 'MMMM Do YYYY, h:mm:ss'
 }; // clock.js ReactDOM.render(
 <Clock />,
 document.getElementById('clock')
 ); ReactDOM.render(
 <Clock dateFormat='h:mm:ss'/>,
 document.getElementById('clock')
 ); Components Lifecycle Props Props are set when created and are immutable.
  52. 52. Component Types Container Fetches the application data and composes the User Interface out of Presentation components. Presentation Takes the data from a Container component and presents it to the user.
  53. 53. confluence-issues-addon public routes views src components containers
  54. 54. Project Components Components of Confluence Issues Add-on
  55. 55. IssuesAppContainer Decides what to display in the page body.
  56. 56. IssuesList Displays the column headings and an IssueListItem for each row.
  57. 57. IssuesListItem Represents a row in the list of issues.
  58. 58. confluence-issues-addon src components containers
  59. 59. confluence-issues-addon src components containers IssueAppContainer.js list IssueList.js IssueListLineItem.js
  60. 60. IssueAppContainer
  61. 61. loadIssues(spaceKey) {
 return (callback) => {
 AP.require('request', function (request) {
 request({
 url: '/rest/api/content',
 data: { … },
 success: (data) => {
 let issues = JSON.parse(data) .results .map(Issues.contentToIssue); callback(localIssues);
 }
 });
 });
 }
 } Load Issues loadIssues
  62. 62. loadIssues(spaceKey) {
 return (callback) => {
 AP.require('request', function (request) {
 request({
 url: '/rest/api/content',
 data: { … },
 success: (data) => {
 let issues = JSON.parse(data) .results .map(Issues.contentToIssue); callback(localIssues);
 }
 });
 });
 }
 } Load Issues loadIssues
  63. 63. loadIssues(spaceKey) {
 return (callback) => {
 AP.require('request', function (request) {
 request({
 url: '/rest/api/content',
 data: { … },
 success: (data) => {
 let issues = JSON.parse(data) .results .map(Issues.contentToIssue); callback(localIssues);
 }
 });
 });
 }
 } Load Issues loadIssues
  64. 64. loadIssues(spaceKey) {
 return (callback) => {
 AP.require('request', function (request) {
 request({
 url: '/rest/api/content',
 data: { … },
 success: (data) => {
 let issues = JSON.parse(data) .results .map(Issues.contentToIssue); callback(localIssues);
 }
 });
 });
 }
 } Load Issues loadIssues
  65. 65. loadIssues(spaceKey) {
 return (callback) => {
 AP.require('request', function (request) {
 request({
 url: '/rest/api/content',
 data: { … },
 success: (data) => {
 let issues = JSON.parse(data) .results .map(Issues.contentToIssue); callback(localIssues);
 }
 });
 });
 }
 } Load Issues loadIssues Reusable function to load issues.
  66. 66. export default class IssueAppContainer extends React.Component { 
 componentDidMount() {
 this.props.loadIssues((issues) => {
 this.setState({issues: issues, loading: false})
 });
 }
 render() {
 let createIssueClick = …;
 if (this.state.loading) {
 return (<Loading/>);
 } else {
 let emptyState = (this.state.issues.length === 0);
 if (emptyState) {
 return (<IssueAppEmpty createIssueClick={createIssueClick}/>)
 } else {
 return (<IssueAppPopulated createIssueClick={createIssueClick}
 issues={this.state.issues}/>)
 }
 }
 }
 } Issue App Components Issue App Issue List Issue Macro
  67. 67. export default class IssueAppContainer extends React.Component { 
 componentDidMount() {
 this.props.loadIssues((issues) => {
 this.setState({issues: issues, loading: false})
 });
 }
 render() {
 let createIssueClick = …;
 if (this.state.loading) {
 return (<Loading/>);
 } else {
 let emptyState = (this.state.issues.length === 0);
 if (emptyState) {
 return (<IssueAppEmpty createIssueClick={createIssueClick}/>)
 } else {
 return (<IssueAppPopulated createIssueClick={createIssueClick}
 issues={this.state.issues}/>)
 }
 }
 }
 } Issue App Components Issue App Issue List Issue Macro
  68. 68. export default class IssueAppContainer extends React.Component { 
 componentDidMount() {
 this.props.loadIssues((issues) => {
 this.setState({issues: issues, loading: false})
 });
 }
 render() {
 let createIssueClick = …;
 if (this.state.loading) {
 return (<Loading/>);
 } else {
 let emptyState = (this.state.issues.length === 0);
 if (emptyState) {
 return (<IssueAppEmpty createIssueClick={createIssueClick}/>)
 } else {
 return (<IssueAppPopulated createIssueClick={createIssueClick}
 issues={this.state.issues}/>)
 }
 }
 }
 } Issue App Components Issue App Issue List Issue Macro
  69. 69. export default class IssueAppContainer extends React.Component { 
 componentDidMount() {
 this.props.loadIssues((issues) => {
 this.setState({issues: issues, loading: false})
 });
 }
 render() {
 let createIssueClick = …;
 if (this.state.loading) {
 return (<Loading/>);
 } else {
 let emptyState = (this.state.issues.length === 0);
 if (emptyState) {
 return (<IssueAppEmpty createIssueClick={createIssueClick}/>)
 } else {
 return (<IssueAppPopulated createIssueClick={createIssueClick}
 issues={this.state.issues}/>)
 }
 }
 }
 } Issue App Components Issue App Issue List Issue Macro Use the state to decide what to render.
  70. 70. IssueList [
 {
 "title": "Build…",
 "type": "task",
 "status": "new", …
 }, …
 ]
  71. 71. Issue App Components Issue App Issue List Issue Macro 
 export default class IssueList extends React.Component {
 render() {
 let issues = this.props.issues;
 let fields = …;
 
 return (
 <table className="list-of-issues">
 <IssueListHeadings fields={fields}/>
 <tbody>
 {issues.map((issue) => {
 return (
 <IssueLineItem key={issue.key} fields={fields} issue={issue}/>)
 })}
 </tbody>
 </table>
 );
 }
 }
  72. 72. Issue App Components Issue App Issue List Issue Macro 
 export default class IssueList extends React.Component {
 render() {
 let issues = this.props.issues;
 let fields = …;
 
 return (
 <table className="list-of-issues">
 <IssueListHeadings fields={fields}/>
 <tbody>
 {issues.map((issue) => {
 return (
 <IssueLineItem key={issue.key} fields={fields} issue={issue}/>)
 })}
 </tbody>
 </table>
 );
 }
 }
  73. 73. Issue App Components Issue App Issue List Issue Macro 
 export default class IssueList extends React.Component {
 render() {
 let issues = this.props.issues;
 let fields = …;
 
 return (
 <table className="list-of-issues">
 <IssueListHeadings fields={fields}/>
 <tbody>
 {issues.map((issue) => {
 return (
 <IssueLineItem key={issue.key} fields={fields} issue={issue}/>)
 })}
 </tbody>
 </table>
 );
 }
 }
  74. 74. Issue App Components Issue App Issue List Issue Macro 
 export default class IssueList extends React.Component {
 render() {
 let issues = this.props.issues;
 let fields = …;
 
 return (
 <table className="list-of-issues">
 <IssueListHeadings fields={fields}/>
 <tbody>
 {issues.map((issue) => {
 return (
 <IssueLineItem key={issue.key} fields={fields} issue={issue}/>)
 })}
 </tbody>
 </table>
 );
 }
 } Presentation components have no state.
  75. 75. Issue App Components Issue App Issue List Issue Macro
  76. 76. Macro This macro shows a list of issues. Issue App Issue List Issue Macro Issue App Components
  77. 77. IssueMacroContainer
  78. 78. Issue App Components Issue App Issue List Issue Macro export default class IssueMacroContainer extends React.Component {
 
 componentDidMount() {
 this.loadIssues((issues) => {
 this.setState({issues: issues, loading: false})
 });
 }
 
 render() {
 if (this.state.loading) {
 return (<Loading/>);
 } else {
 let emptyState = (this.state.issues.length === 0);
 if (emptyState) {
 return (<IssueMacroEmpty/>)
 } else {
 return (<IssueMacroPopulated issues={this.state.issues}/>)
 }
 }
 }
 }
  79. 79. Issue App Components Issue App Issue List Issue Macro export default class IssueMacroContainer extends React.Component {
 
 componentDidMount() {
 this.loadIssues((issues) => {
 this.setState({issues: issues, loading: false})
 });
 }
 
 render() {
 if (this.state.loading) {
 return (<Loading/>);
 } else {
 let emptyState = (this.state.issues.length === 0);
 if (emptyState) {
 return (<IssueMacroEmpty/>)
 } else {
 return (<IssueMacroPopulated issues={this.state.issues}/>)
 }
 }
 }
 }
  80. 80. Issue App Components Issue App Issue List Issue Macro export default class IssueMacroContainer extends React.Component {
 
 componentDidMount() {
 this.loadIssues((issues) => {
 this.setState({issues: issues, loading: false})
 });
 }
 
 render() {
 if (this.state.loading) {
 return (<Loading/>);
 } else {
 let emptyState = (this.state.issues.length === 0);
 if (emptyState) {
 return (<IssueMacroEmpty/>)
 } else {
 return (<IssueMacroPopulated issues={this.state.issues}/>)
 }
 }
 }
 }
  81. 81. Issue App Components Issue App Issue List Issue Macro export default class IssueMacroContainer extends React.Component {
 
 componentDidMount() {
 this.loadIssues((issues) => {
 this.setState({issues: issues, loading: false})
 });
 }
 
 render() {
 if (this.state.loading) {
 return (<Loading/>);
 } else {
 let emptyState = (this.state.issues.length === 0);
 if (emptyState) {
 return (<IssueMacroEmpty/>)
 } else {
 return (<IssueMacroPopulated issues={this.state.issues}/>)
 }
 }
 }
 } Same concept as IssueAppContainer.
  82. 82. Entry Points Mapping Extensions to Components
  83. 83. MacroFull Page Dialog
  84. 84. App.js Macro.js NewIssueDialog.js confluence-issues-addon src components containers entry-points
  85. 85. Entry Point Import Context Render import IssueMacroContainer from '../containers/IssueMacroContainer';
 
 let spaceKey = queryString.parse(location.search)["spaceKey"];
 
 ReactDOM.render(<IssueMacroContainer
 loadIssues={Issues.loadIssues(spaceKey)}/>, document.getElementById("list-issues"));
  86. 86. Entry Point Import Context Render import IssueMacroContainer from '../containers/IssueMacroContainer';
 
 let spaceKey = queryString.parse(location.search)["spaceKey"];
 
 ReactDOM.render(<IssueMacroContainer
 loadIssues={Issues.loadIssues(spaceKey)}/>, document.getElementById("list-issues"));
  87. 87. import IssueMacroContainer from '../containers/IssueMacroContainer';
 
 let spaceKey = queryString.parse(location.search)["spaceKey"];
 
 ReactDOM.render(<IssueMacroContainer
 loadIssues={Issues.loadIssues(spaceKey)}/>, document.getElementById("list-issues")); Entry Point Import Context Render
  88. 88. import IssueMacroContainer from '../containers/IssueMacroContainer';
 
 let spaceKey = queryString.parse(location.search)["spaceKey"];
 
 ReactDOM.render(<IssueMacroContainer
 loadIssues={Issues.loadIssues(spaceKey)}/>, document.getElementById("list-issues")); Entry Point Import Context Render An entry point will render the top component.
  89. 89. webpack takes modules with dependencies and generates static assets representing those modules. http://webpack.github.io/
  90. 90. confluence-issues-addon webpack.config.js
  91. 91. Webpack Configuration Config Entry Output module.exports = {
 resolve: {root: [__dirname + path.sep + 'assets']},
 devtool: 'source-map',
 entry:{
 app: './src/entry-points/App.js',
 dialog: './src/entry-points/NewIssueDialog.js',
 macro: './src/entry-points/Macro.js',
 },
 output: {
 path: "./public/js",
 filename: '[name].js'
 },
 plugins: [ … ],
 module: {
 loaders: [ … ]
 }
 };
  92. 92. module.exports = {
 resolve: {root: [__dirname + path.sep + 'assets']},
 devtool: 'source-map',
 entry:{
 app: './src/entry-points/App.js',
 dialog: './src/entry-points/NewIssueDialog.js',
 macro: './src/entry-points/Macro.js',
 },
 output: {
 path: "./public/js",
 filename: '[name].js'
 },
 plugins: [ … ],
 module: {
 loaders: [ … ]
 }
 }; Webpack Configuration Config Entry Output
  93. 93. module.exports = {
 resolve: {root: [__dirname + path.sep + 'assets']},
 devtool: 'source-map',
 entry:{
 app: './src/entry-points/App.js',
 dialog: './src/entry-points/NewIssueDialog.js',
 macro: './src/entry-points/Macro.js',
 },
 output: {
 path: "./public/js",
 filename: '[name].js'
 },
 plugins: [ … ],
 module: {
 loaders: [ … ]
 }
 }; Webpack Configuration Config Entry Output
  94. 94. module.exports = {
 resolve: {root: [__dirname + path.sep + 'assets']},
 devtool: 'source-map',
 entry:{
 app: './src/entry-points/App.js',
 dialog: './src/entry-points/NewIssueDialog.js',
 macro: './src/entry-points/Macro.js',
 },
 output: {
 path: "./public/js",
 filename: '[name].js'
 },
 plugins: [ … ],
 module: {
 loaders: [ … ]
 }
 }; Webpack Configuration Config Entry Output Webpack will bundle your components into static files that a browser will understand.
  95. 95. confluence-issues-addon public js macro.js
  96. 96. {{!< layout}}
 <div id="list-issues"/>
 <script src="/js/macro.js"></script> Recap Component State Entry HTML
  97. 97. Building Using NPM to build your App
  98. 98. Building Package Bundle Start {
 "name": "confluence-issues-addon",
 "version": "0.0.1",
 "private": true,
 "scripts": {
 "start": "node app.js",
 "bundle": "webpack",
 "watch": "webpack --watch",
 "prestart": "webpack"
 },
 "dependencies": { … },
 "devDependencies": {
 "babel-core": "^6.9.0",
 "babel-loader": "^6.2.4",
 "babel-preset-es2015": "^6.9.0",
 "babel-preset-react": "^6.5.0",
 "webpack": "^1.13.0"
 }
 }
  99. 99. {
 "name": "confluence-issues-addon",
 "version": "0.0.1",
 "private": true,
 "scripts": {
 "start": "node app.js",
 "bundle": "webpack",
 "watch": "webpack --watch",
 "prestart": "webpack"
 },
 "dependencies": { … },
 "devDependencies": {
 "babel-core": "^6.9.0",
 "babel-loader": "^6.2.4",
 "babel-preset-es2015": "^6.9.0",
 "babel-preset-react": "^6.5.0",
 "webpack": "^1.13.0"
 }
 } Building Package Bundle Start
  100. 100. {
 "name": "confluence-issues-addon",
 "version": "0.0.1",
 "private": true,
 "scripts": {
 "start": "node app.js",
 "bundle": "webpack",
 "watch": "webpack --watch",
 "prestart": "webpack"
 },
 "dependencies": { … },
 "devDependencies": {
 "babel-core": "^6.9.0",
 "babel-loader": "^6.2.4",
 "babel-preset-es2015": "^6.9.0",
 "babel-preset-react": "^6.5.0",
 "webpack": "^1.13.0"
 }
 } Building Package Bundle Start
  101. 101. {
 "name": "confluence-issues-addon",
 "version": "0.0.1",
 "private": true,
 "scripts": {
 "start": "node app.js",
 "bundle": "webpack",
 "watch": "webpack --watch",
 "prestart": "webpack"
 },
 "dependencies": { … },
 "devDependencies": {
 "babel-core": "^6.9.0",
 "babel-loader": "^6.2.4",
 "babel-preset-es2015": "^6.9.0",
 "babel-preset-react": "^6.5.0",
 "webpack": "^1.13.0"
 }
 } Building Package Bundle Start Babel is used to transpile the extended JavaScript.
  102. 102. Transpiling
  103. 103. ES2015 Extensions, modules, dependencies, etc Standalone files, compatible with browsers, etc Transpiling
  104. 104. Run a the webpack script Use npm run bundle to run the webpack script we defined in the package.json file. Building Package Bundle Start
  105. 105. Building Package Bundle Start Run a the webpack script Use npm run bundle to run the webpack script we defined in the package.json file.
  106. 106. Create the static JavaScript files These files are self contained and browser compatible JavaScript resources. Building Package Bundle Start
  107. 107. Client app.js Refers to the application entry point component Server app.js Default name for the Node.js server entry point. Two Apps
  108. 108. Create the project Use npm start to start the node app, automatically transpiling the client components. Building Package Bundle Start
  109. 109. Building Package Bundle Start Create the project Use npm start to start the node app, automatically transpiling the client components.
  110. 110. Building Package Bundle Start Create the project Use npm start to start the node app, automatically transpiling the client components.
  111. 111. Building Package Bundle Start Create the project Use npm start to start the node app, automatically transpiling the client components.
  112. 112. Node JS Uses NPM to package both server and client side modules. Manually Package You can still use NPM to bundle your client side components Dev Loop Restart your add-on service and call webpack on each change.
  113. 113. Recap Quick recap of what we have learnt so far
  114. 114. Recap Component State Entry class Clock extends React.Component {
 constructor(props) {
 super(props);
 this.state = {date: moment()};
 }
 
 componentDidMount() {
 this.timerID = setInterval(() => this.tick(), 1000);
 }
 
 componentWillUnmount() {
 clearInterval(this.timerID);
 }
 
 tick() {
 this.setState({date: moment()});
 }
 
 render() {
 return (<span>{this.state.date.format(this.props.dateFormat)}</span>);
 }
 }
 
 Clock.defaultProps = {
 'dateFormat': 'MMMM Do YYYY, h:mm:ss'
 };
 
 export default Clock; Building
  115. 115. class Clock extends React.Component {
 constructor(props) {
 super(props);
 this.state = {date: moment()};
 }
 
 componentDidMount() {
 this.timerID = setInterval(() => this.tick(), 1000);
 }
 
 componentWillUnmount() {
 clearInterval(this.timerID);
 }
 
 tick() {
 this.setState({date: moment()});
 }
 
 render() {
 return (<span>{this.state.date.format(this.props.dateFormat)}</span>);
 }
 }
 
 Clock.defaultProps = {
 'dateFormat': 'MMMM Do YYYY, h:mm:ss'
 };
 
 export default Clock; Recap Component State Entry Building
  116. 116. class Clock extends React.Component {
 constructor(props) {
 super(props);
 this.state = {date: moment()};
 }
 
 componentDidMount() {
 this.timerID = setInterval(() => this.tick(), 1000);
 }
 
 componentWillUnmount() {
 clearInterval(this.timerID);
 }
 
 tick() {
 this.setState({date: moment()});
 }
 
 render() {
 return (<span>{this.state.date.format(this.props.dateFormat)}</span>);
 }
 }
 
 Clock.defaultProps = {
 'dateFormat': 'MMMM Do YYYY, h:mm:ss'
 };
 
 export default Clock; Recap Component State Entry Building
  117. 117. class Clock extends React.Component {
 constructor(props) {
 super(props);
 this.state = {date: moment()};
 }
 
 componentDidMount() {
 this.timerID = setInterval(() => this.tick(), 1000);
 }
 
 componentWillUnmount() {
 clearInterval(this.timerID);
 }
 
 tick() {
 this.setState({date: moment()});
 }
 
 render() {
 return (<span>{this.state.date.format(this.props.dateFormat)}</span>);
 }
 }
 
 Clock.defaultProps = {
 'dateFormat': 'MMMM Do YYYY, h:mm:ss'
 };
 
 export default Clock; Recap Component State Entry Building
  118. 118. ReactDOM.render(
 <Clock dateFormat='MMMM Do YYYY, h:mm:ss'/>,
 document.getElementById('clock')
 ); Recap Component State Entry Building
  119. 119. module.exports = {
 resolve: { root: [__dirname + path.sep + 'assets'] },
 devtool: 'source-map',
 entry:{
 router: './assets/js/Clock'
 },
 output: {
 path: "./public/js",
 filename: '[name].js'
 },
 plugins: [ … ],
 module: {
 loaders: [ …]
 }
 }; Recap Component State Entry Building
  120. 120. module.exports = {
 resolve: { root: [__dirname + path.sep + 'assets'] },
 devtool: 'source-map',
 entry:{
 router: './assets/js/Clock'
 },
 output: {
 path: "./public/js",
 filename: '[name].js'
 },
 plugins: [ … ],
 module: {
 loaders: [ …]
 }
 }; Recap Component State Entry Building
  121. 121. {{!< layout}}
 <div id="clock"/>
 <script src="/js/clock.js"></script> Recap Component State Entry Building
  122. 122. Recap Component State Entry Building NPM Use npm to manage webpack dependencies and development lifecycle.
  123. 123. Onward Related Concepts and Advanced Topics
  124. 124. Onward AtlasKit Other Extensions React Components AtlasKit components are React Components. NPM Modules Define a dependency on the AtlasKit component and import it in to your own! AtlasKit
  125. 125. Onward AtlasKit Other Extensions
  126. 126. Onward AtlasKit Other Extensions
  127. 127. Onward AtlasKit Other Extensions
  128. 128. Onward AtlasKit Other Extensions
  129. 129. Thank you! MATTHEW JENSEN | TEAM LEAD | ATLASSIAN | @MATTHEWJENSEN

×