One place for hosting & domains

      August 2020

      How To Debug React Components Using React Developer Tools


      The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

      Introduction

      Since React apps are made to scale and grow quickly, it’s easy for subtle bugs to infiltrate your code. The React Developer Tools browser extension can help you track down these bugs by giving you more insight into the current state for each component. React Developer Tools gives you an interface for exploring the React component tree along with the current props, state, and context for individual components. React Developer Tools also lets you determine which components are re-rendering and can generate graphs to show how long individual components take to render. You can use this information to track down inefficient code or to optimize data-heavy components.

      This tutorial begins by installing the React Developer Tools browser extension. You’ll then build a text analyzer as a test application, which will take a block of text and display information such as word count, character count, and character usage. Finally, you will use React Developer Tools to explore the text analyzer’s components and keep track of the changing props and context. The examples will use the Chrome browser, but you can also use the plugin for Firefox.

      By the end of this tutorial, you’ll be able to start using the React Developer Tools to debug and explore any React project.

      Prerequisites

      In this step, you’ll install the React Developer Tools broswer extension in Chrome. You’ll use the developer tools in the Chrome JavaScript console to explore the component tree of the debug-tutorial project you made in the Prerequisites. This step will use Chrome, but the steps will be nearly identical for installing the React Developer Tools as an add-on in Firefox.

      By the end of this step, you’ll have the React Developer Tools installed in your browser and you’ll be able to explore and filter components by name.

      The React Developer Tools is a plugin for the Chrome and Firefox browser. When you add the extension, you are adding additional tools to the developer console. Visit the Chrome plugin page for React Developer Tools to install the extension.

      Click on the Add to Chrome button. Then click on the Add extension button to confirm:

      Chrome add extension button

      Chrome will install the extension, and a success message and a new icon will appear in the upper right corner of your browser next to the address bar:

      Chrome success message

      If the icon does not appear, you can add it by clicking on the puzzle piece, then clicking on the pushpin icon by the React Developer Tools:

      Pin extension

      When you are on a page that does not have any React components, the icon will appear gray. However, if you are on a page with React components, the icon will appear blue and green. If you click on the icon, it will indicate that the application is running a production version of React.

      Visit digitalocean.com, to find that the homepage is running a production version of React:

      DigitalOcean React Production Build information

      Now that you are on a website that uses React, open the console to access the React Developer Tools. Open the console by either right-clicking and inspecting an element or by opening the toolbar by clicking View > Developer > JavaScript console.

      When you open the console, you’ll find two new tabs: Components and Profiler:

      Console Open

      The Components tab will show the current React component tree, along with any props, state, or context. The Profiler tab lets you record interactions and measure component rendering. You’ll explore the Profiler tab in Step 3.

      Click on the Components tab to see the current component tree.

      Since this is a production build, the code will be minified and the components will not have descriptive names:

      Components for digitalocean.com in the console

      Now that you’ve tried out React Developer Tools on a working website, you can use it on your test application. If you haven’t started your debug-tutorial application yet, go to a terminal window and run npm start from the root of the project.

      Open a browser to http://localhost:3000.

      Notice that the icon for React Developer Tools is now red and white. If you click on the React Developer Tools icon, you’ll see a warning that the page is in development mode. Since you are still working on the sample application, this is expected.

      Open the console and you’ll find the name of the App component in the Components tab.

      Base Component

      There’s not a lot of information yet, but as you build out the project in the next step, you’ll see all of your components forming a navigable tree.

      In this step, you added the React Developer Tools extension to Chrome. You activated the tools on both a production and a development page, and you briefly explored your debug-tutorial project in the Components tab. In the next step, you’ll build the text analyzer that you’ll use to try out the features of the React Developer Tools.

      Step 2 — Identifying Real-Time Component Props and Context

      In this step, you’ll build a small application to analyze a block of text. The app will determine and report the word count, character count, and character frequency of the text in the input field. As you build the application, you’ll use React Developer Tools to explore the current state and props of each component. You’ll also use React Developer Tools to view the current context in deeply nested components. Finally, you’ll use the tools to identify components that re-render as state changes.

      By the end of this step, you’ll be able to use the React Developer Tools to explore a live application and to observe the current props and state without console statements or debuggers.

      To start, you will create an input component that will take a large amount of text.

      Open the App.js file:

      • nano src/components/App/App.js

      Inside the component, add a div with a class of wrapper, then create a <label> element surrounding a <textarea> element:

      debug-tutorial/src/components/App/App.js

      import React from 'react';
      import './App.css';
      
      function App() {
        return(
          <div className="wrapper">
           <label htmlFor="text">
             Add Your Text Here:
             <br>
             <textarea
               id="text"
               name="text"
               rows="10"
               cols="100"
             >
             </textarea>
            </label>
          </div>
        )
      }
      
      export default App;
      

      This will be the input area for your user. The htmlFor attribute links the label element to elements with an id of text using JSX. You also give the <textarea> component 10 rows and 100 columns to provide room for a large amount of text.

      Save and close the file. Next, open App.css:

      • nano src/components/App/App.css

      Add some styling to the application by replacing the contents with the following:

      debug-tutorial/src/components/App.App.css

      .wrapper {
          padding: 20px;
      }
      
      .wrapper button {
          background: none;
          border: black solid 1px;
          cursor: pointer;
          margin-right: 10px;
      }
      
      .wrapper div {
          margin: 20px 0;
      }
      

      Here you add some padding to the wrapper class, then simplify child <button> elements by removing the background color and adding some margin. Finally, you add a small margin to child <div> elements. These styles will apply to components you will build to display information about the text.

      Save the file. When you do, the browser will refresh and you’ll see the input:

      Text area

      Open App.js:

      • nano src/components/App/App.js

      Next, create a context to hold the value from the <textarea> element. Capture the data using the useState Hook:

      debug-tutorial/src/components/App/App.js

      import React, { createContext, useState } from 'react';
      import './App.css';
      
      export const TextContext = createContext();
      
      function App() {
        const [text, setText] = useState('');
      
        return(
          <TextContext.Provider value={text}>
            <div className="wrapper">
              <label htmlFor="text">
                Add Your Text Here:
                <br>
                <textarea
                  id="text"
                  name="text"
                  rows="10"
                  cols="100"
                  onChange={e => setText(e.target.value)}
                >
                </textarea>
              </label>
            </div>
          </TextContext.Provider>
        )
      }
      
      export default App;
      

      Be sure to export TextContext, then wrap the whole component with the TextContext.Provider. Capture the data by adding an onChange prop to your <textarea> element.

      Save the file. The browser will reload. Be sure that you have React Developer Tools open and notice that App component now shows theContext.Provider as a child component.

      Component context in React Developer Tools

      The component by default has a generic name—Context—but you can change that by adding a displayName property to the generated context. Inside App.js, add a line where you set the displayName to TextContext:

      debug-tutorial/src/components/App/App.js

      import React, { createContext, useState } from 'react';
      import './App.css';
      
      export const TextContext = createContext();
      TextContext.displayName="TextContext";
      
      function App() {
          ...
      }
      
      export default App;
      

      It is not necessary to add a displayName, but it does help to navigate components when analyzing the component tree in the console. You will also see the value of the useState Hook in the side bar. Type some text in the input and you’ll see the updated value in React Developer Tools under the hooks section on the App component.

      Update Value in Developer Tools

      The Hook also has a generic name of State, but this is not as easy to update as the context. There is a useDebugValue Hook, but it only works on custom Hooks and is not recommended for all custom Hooks.

      In this case, the state for the App component is the prop to TextContext.Provider. Click on the TextContext.Provider in the React Developer Tools and you’ll see that the value also reflects the input value that you set with the state:

      Updated value for the context

      React Developer Tools is showing you real time prop and context information, and the value will grow as you add components.

      Next, add a component called TextInformation. This component will be a container for the components with specific data analysis, such as the word count.

      First, make the directory:

      • mkdir src/components/TextInformation

      Then open TextInformation.js in your text editor.

      • nano src/components/TextInformation/TextInformation.js

      Inside the component, you will have three separate components: CharacterCount, WordCount, and CharacterMap. You’ll make these components in just a moment.

      The TextInformation component will use the useReducer Hook to toggle the display of each component. Create a reducer function that toggles the display value of each component and a button to toggle each component with an onClick action:

      debug-tutorial/src/components/TextInformation/TextInformation.js

      import React, { useReducer } from 'react';
      
      const reducer = (state, action) => {
        return {
          ...state,
          [action]: !state[action]
        }
      }
      export default function TextInformation() {
        const [tabs, toggleTabs] = useReducer(reducer, {
          characterCount: true,
          wordCount: true,
          characterMap: true
        });
      
        return(
          <div>
            <button onClick={() => toggleTabs('characterCount')}>Character Count</button>
            <button onClick={() => toggleTabs('wordCount')}>Word Count</button>
            <button onClick={() => toggleTabs('characterMap')}>Character Map</button>
          </div>
        )
      }
      

      Notice that your useReducer Hook starts with an object that maps each key to a boolean. The reducer function uses the spread operator to preserve the previous value while setting a new value using the action parameter.

      Save and close the file. Then open App.js:

      • nano src/components/App/App.js

      Add in your new component:

      debug-tutorial/src/components/App/App.js

      import React, { createContext, useState } from 'react';
      import './App.css';
      import TextInformation from '../TextInformation/TextInformation';
      
      ...
      
      function App() {
        const [text, setText] = useState('');
      
        return(
          <TextContext.Provider value={text}>
            <div className="wrapper">
              <label htmlFor="text">
                Add Your Text Here:
                <br>
                <textarea
                  id="text"
                  name="text"
                  rows="10"
                  cols="100"
                  onChange={e => setText(e.target.value)}
                >
                </textarea>
              </label>
              <TextInformation />
            </div>
          </TextContext.Provider>
        )
      }
      
      export default App;
      

      Save and close the file. When you do, the browser will reload, and you’ll see the updated component. If you click on TextInformation in React Developer Tools, you’ll see the value update on each button click:

      Update Reducer on Click

      Now that you have the container component, you’ll need to create each informational component. Each component will take a prop called show. If show is falsy, the component will return null. The components will consume the TextContext, analyze the data, and display the result.

      To start, create the CharacterCount component.

      First, make a new directory:

      • mkdir src/components/CharacterCount

      Then, open CharacterCount.js in your text editor:

      • nano src/components/CharacterCount/CharacterCount.js

      Inside the component, create a function that uses the show prop and displays null if show is falsy:

      debug-tutorial/src/components/CharacterCount/CharacterCount.js

      import React, { useContext } from 'react';
      import PropTypes from 'prop-types';
      import { TextContext } from '../App/App';
      
      export default function CharacterCount({ show }) {
        const text = useContext(TextContext);
      
        if(!show) {
          return null;
        }
      
        return(
          <div>
            Character Count: {text.length}
          </div>
        )
      }
      
      CharacterCount.proTypes = {
        show: PropTypes.bool.isRequired
      }
      

      Inside the CharacterCount function, you assign the value of TextContext to a variable using the useContext Hook. You then return a <div> that shows the character count using the length method. Finally, PropTypes adds a weak typing system to provide some enforcement to make sure the wrong prop type is not passed.

      Save and close the file. Open TextInformation.js:

      • nano src/components/TextInformation/TextInformation.js

      Import CharacterCount and add the component after the buttons, passing tabs.characterCount as the show prop:

      debug-tutorial/src/components/TextInformation/TextInformation.js

      import React, { useReducer } from 'react';
      import CharacterCount from '../CharacterCount/CharacterCount';
      
      const reducer = (state, action) => {
        return {
          ...state,
          [action]: !state[action]
        }
      }
      
      export default function TextInformation() {
        const [tabs, toggleTabs] = useReducer(reducer, {
          characterCount: true,
          wordCount: true,
          characterMap: true
        });
      
        return(
          <div>
            <button onClick={() => toggleTabs('characterCount')}>Character Count</button>
            <button onClick={() => toggleTabs('wordCount')}>Word Count</button>
            <button onClick={() => toggleTabs('characterMap')}>Character Map</button>
            <CharacterCount show={tabs.characterCount} />
          </div>
        )
      }
      

      Save the file. The browser will reload and you’ll see the component in the React Developer Tools. Notice that as you add words in the input, the context will update. If you toggle the component, you’ll see the props update after each click:

      Adding text and toggling

      You can also manually add or change a prop by clicking on the property and updating the value:

      Manually Changing Props

      Next, add a WordCount component.

      Create the directory:

      • mkdir src/components/WordCount

      Open the file in a text editor:

      • nano src/components/WordCount/WordCount.js

      Make a component that is similar to CharacterCount, but use the split method on spaces to create an array of words before showing the length:

      debug-tutorial/src/components/WordCount/WordCount.js

      import React, { useContext } from 'react';
      import PropTypes from 'prop-types';
      import { TextContext } from '../App/App';
      
      export default function WordCount({ show }) {
        const text = useContext(TextContext);
      
        if(!show) {
          return null;
        }
      
        return(
          <div>
            Word Count: {text.split(' ').length}
          </div>
        )
      }
      
      WordCount.proTypes = {
        show: PropTypes.bool.isRequired
      }
      

      Save and close the file.

      Finally, create a CharacterMap component. This component will show how often a specific character is used in a block of text. It will then sort the characters by frequency in the passage and display the results.

      First, make the directory:

      • mkdir src/components/CharacterMap

      Next, open CharacterMap.js in a text editor:

      • nano src/components/CharacterMap/CharacterMap.js

      Import and use the TextContext component and use the show prop to display results as you did in the previous components:

      debug-tutorial/src/components/CharacterMap/CharacterMap.js

      import React, { useContext } from 'react';
      import PropTypes from 'prop-types';
      import { TextContext } from '../App/App';
      
      export default function CharacterMap({ show }) {
        const text = useContext(TextContext);
      
        if(!show) {
          return null;
        }
      
        return(
          <div>
            Character Map: {text.length}
          </div>
        )
      }
      
      CharacterMap.proTypes = {
        show: PropTypes.bool.isRequired
      }
      

      This component will need a slightly more complicated function to create the frequency map for each letter. You’ll need to go through each character and increment a value anytime there is a repeat. Then you’ll need to take that data and sort it so that the most frequent letters are at the top of the list.

      To do this, add the following highlighted code:

      debug-tutorial/src/components/CharacterMap/CharacterMap.js

      
      import React, { useContext } from 'react';
      import PropTypes from 'prop-types';
      import { TextContext } from '../App/App';
      
      function itemize(text){
       const letters = text.split('')
         .filter(l => l !== ' ')
         .reduce((collection, item) => {
           const letter = item.toLowerCase();
           return {
             ...collection,
             [letter]: (collection[letter] || 0) + 1
           }
         }, {})
       return Object.entries(letters)
         .sort((a, b) => b[1] - a[1]);
      }
      
      export default function CharacterMap({ show }) {
        const text = useContext(TextContext);
      
        if(!show) {
          return null;
        }
      
        return(
          <div>
           Character Map:
           {itemize(text).map(character => (
             <div key={character[0]}>
               {character[0]}: {character[1]}
             </div>
           ))}
          </div>
        )
      }
      
      CharacterMap.proTypes = {
        show: PropTypes.bool.isRequired
      }
      

      In this code, you create a function called itemize that splits the text into an array of characters using the split() string method. Then you reduce the array to an object by adding the character and then incrementing the count for each subsequent character. Finally, you convert the object to an array of pairs using Object.entries and sort to put the most used characters at the top.

      After you create the function, you pass the text to the function in the render method and map over the results to display the character—array value [0]—and the count—array value [1]—inside a <div>.

      Save and close the file. This function will give you an opportunity to explore some performance features of the React Developer Tools in the next section.

      Next, add the new components to TextInformation and look at the values in React Developer Tools.

      Open TextInformation.js:

      • nano src/components/TextInformation/TextInformation.js

      Import and render the new components:

      debug-tutorial/src/components/TextInformation/TextInformation.js

      import React, { useReducer } from 'react';
      import CharacterCount from '../CharacterCount/CharacterCount';
      import CharacterMap from '../CharacterMap/CharacterMap';
      import WordCount from '../WordCount/WordCount';
      
      const reducer = (state, action) => {
        return {
          ...state,
          [action]: !state[action]
        }
      }
      
      export default function TextInformation() {
        const [tabs, toggleTabs] = useReducer(reducer, {
          characterCount: true,
          wordCount: true,
          characterMap: true
        });
      
        return(
          <div>
            <button onClick={() => toggleTabs('characterCount')}>Character Count</button>
            <button onClick={() => toggleTabs('wordCount')}>Word Count</button>
            <button onClick={() => toggleTabs('characterMap')}>Character Map</button>
            <CharacterCount show={tabs.characterCount} />
            <WordCount show={tabs.wordCount} />
            <CharacterMap show={tabs.characterMap} />
          </div>
        )
      }
      

      Save and close the file. When you do, the browser will refresh, and if you add in some data, you’ll find the character frequency analysis in the new components:

      CharacterMap Component in React Developer Tools

      In this section, you used React Developer Tools to explore the component tree. You also learned how to see the real-time props for each component and how to manually change the props using the developer tools. Finally, you viewed the context for the component change with input.

      In the next section, you’ll use the React Developer Tools Profiler tab to identify components that have long render times.

      Step 3 — Tracking Component Rendering Across Interactions

      In this step, you’ll use the React Developer Tools profiler to track component rendering and re-rendering as you use the sample application. You’ll navigate flamegraphs, or visualizations of your app’s relevant optimization metrics, and use the information to identify inefficient components, reduce rendering time, and increase application speed.

      By the end of this step, you’ll know how to identify components that render during user interactions and how to compose components to reduce inefficient rendering.

      A quick way to see how components change each other is to enable highlighting when a component is re-rendered. This will give you a visual overview of how the components respond to changing data.

      In React Developer Tools, click on the Settings icon. It will look like a gear:

      Settings icon

      Then select the option under General that says Highlight updates when components render.

      Highlight changes

      When you make any changes, React Developer Tools will highlight components that re-render. For example, when you change input, every component re-renders because the data is stored on a Hook at the root level and every change will re-render the whole component tree.

      Notice the highlighting around the components, including the top of the screen around the root component:

      Highlighting Text

      Compare that to how the components re-render when you click on one of the buttons to toggle the data. If you click one of the buttons, the components under TextInformation will re-render, but not the root component:

      Rerendering lower components only

      Showing the re-renders will give you a quick idea of how the components are related, but it doesn’t give you a lot of data to analyze specific components. To gain more insight, let’s look at the profiler tools.

      The profiler tools are designed to help you measure precisely how long each component takes to render. This can help you identify components that may be slow or process intense.

      Re-open the settings and uncheck the box for Highlight updates when components render. Then click on the Profiler tab in the console.

      To use the profiler, click the blue circle on the left of the screen to begin recording and click it again when you are finished:

      Start profiling

      When you stop recording, you’ll find a graph of the component changes including, how long each item took to render.

      To get a good sense of the relative efficiency of the components, paste in the Wikipedia page for Creative Commons. This text is long enough to give interesting results, but not so big that it will crash the application.

      After pasting in the text, start the profiler, then make a small change to the input. Stop profiling after the component has finished re-rendering. There will be a long pause, because the application is handling a long re-rendering:

      Adding a change with a lot of text

      When you end the recording, React Developer Tools will create a flamegraph that shows every component that re-rendered and how long it took to re-render each component.

      In this case, every keypress from the word “Change” causes a re-render. More importantly, it shows how long each render takes and why there was a long delay. The components App, TextContext.Provider, and TextInformation take about .2 milliseconds to rerender. But the CharacterMap component takes around 1 second per keystroke to re-render because of the complicated data parsing in the itemize function.

      In the display, each yellow bar is a new keystroke. You can replay the sequence one at a time by clicking on each bar. Notice that there is slight variation in the render times, but the CharacterMap is consistently slow:

      Looking at the flamegraph

      You can get more information by selecting the option Record why each component rendered while profiling. under the Profiler section of the settings.

      Try toggling the Word Count component and notice how long the changes take. The application still lags even though you haven’t changed the text content:

      Word Count toggle flamegraph

      Now when you hover your cursor over a component, you’ll find that it includes a reason the component re-rendered. In this case, the reason the component changed is The parent component rendered. That’s a problem for the CharacterMap component. CharacterMap is doing an expensive calculation every time the parent changes, even if the props and the context do not change. That is, it’s recalculating data even though the data is identical to the previous render.

      Click on the Ranked tab and you’ll find how much more time CharacterMap takes when compared to all other components:

      Ranked Tab

      React Developer Tools have helped isolate a problem: the CharacterMap component re-renders and performs an expensive calculation anytime any parent component changes.

      There are multiple ways to solve the problem, but they all involve some sort of caching via memoization, a process by which already calculated data is remembered rather than recalculated. You can either use a library like lodash/memoize or memoize-one to cache the results of the itemize function, or you can use the built in React memo function to memoize the whole component.

      If you use the React memo, the function will only re-render if the props or context change. In this case, you’ll use React memo. In general, you should memoize the data itself first since it’s a more isolated case, but there are some interesting changes in the React Developer Tools if you memoize the whole component, so you’ll use that approach in this tutorial.

      Open CharacterMap.js:

      • nano src/components/CharacterMap/CharacterMap.js

      Import memo from React, then pass the entire function into the memo function:

      debug-tutorial/src/components/CharacterMap/CharacterMap.js

      import React, { memo, useContext } from 'react';
      import PropTypes from 'prop-types';
      import { TextContext } from '../App/App';
      
      ...
      
      function CharacterMap({ show }) {
        const text = useContext(TextContext);
      
        if(!show) {
          return null;
        }
      
        return(
          <div>
           Character Map:
           {itemize(text).map(character => (
             <div key={character[0]}>
               {character[0]}: {character[1]}
             </div>
           ))}
          </div>
        )
      }
      
      CharacterMap.proTypes = {
        show: PropTypes.bool.isRequired
      }
      
      export default memo(CharacterMap);
      
      

      You move the export default line to the end of the code in order to pass the component to memo right before exporting. After that, React will compare the props before re-rendering.

      Save and close the file. The browser will reload, and when you toggle the WordCount, the component will update much faster. This time, CharacterMap does not re-render. Instead, in React Developer Tools, you’ll see a gray rectangle showing re-rendering was prevented.

      React Developer Tools showing that CharacterMap did not re-render

      If you look at the Ranked tab, you’ll find that both the CharacterCount and the WordCount re-rendered, but for different reasons. Since CharacterCount is not memoized, it re-rendered because the parent changed. The WordCount re-rendered because the props changed. Even if it was wrapped in memo, it would still rerender.

      Ranked view of memoized app

      Note: Memoizing is helpful, but you should only use it when you have a clear performance problem, as you did in this case. Otherwise, it can create a performance problem: React will have to check props every time it re-renders, which can cause a delay on smaller components.

      In this step, you used the profiler to identify re-renders and componenent re-rendering. You also used flamegraphs and ranked graphs to identify components that are slow to re-render and then used the memo function to prevent re-rendering when there are no changes to the props or context.

      Conclusion

      The React Developer Tools browser extension gives you a powerful set of utilities to explore your components in their applications. With these tools, you’ll be able to explore a component’s state and identify bugs using real data without console statements or debuggers. You can also use the profiler to explore how components interact with each other, allowing you to identify and optimize components that have slow rendering in your full application. These tools are a crucial part of the development process and give you an opportunity to explore the components as part of an application and not just as static code.

      If you would like to learn more about debugging JavaScript, see our article on How To Debug Node.js with the Built-In Debugger and Chrome DevTools. For more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.



      Source link

      How To Create Your First Cross-Platform Desktop Application with Electron on macOS


      The author selected the Apache Software Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      Electron is an open source framework for creating native applications with web technologies like JavaScript, HTML, and CSS. It combines support for building and running applications cross platform on Mac, Windows, and Linux. Many popular desktop applications have been built using Electron, such as Visual Studio Code, WhatsApp, Twitch, Slack, and Figma.

      Electron facilitates designing more complex application features like automatic updates or native menus, which means that developers can focus on the core design of their application. Further, Electron is an open source project maintained by GitHub with an active community of contributors.

      In this tutorial, you’ll first set up a project and install Electron. After that you’ll create your first “Hello World!” application using Electron and customize it. You’ll implement graceful window setup and create new windows for the application. After following all of these steps, you will have an Electron cross-platform desktop application on macOS.

      Prerequisites

      To complete this tutorial, you will need:

      Note: This tutorial was tested on macOS 10.15.3.

      Step 1 — Creating the Project

      First you’ll install Electron to your machine and create the project folder to build the desktop application.

      To start the Electron installation process, create the project folder called hello-world and navigate to the folder with the following commands:

      • mkdir hello-world
      • cd hello-world

      Next, you’ll initiate your project by creating the package.json file.

      The package.json file is an essential part of a Node.js application, it performs the following:

      • Lists the packages that your project depends on.
      • Specifies the package version your project can use.

      To create the package.json file, run the following command:

      You will be asked a series of questions, starting with the package name. You can use the default application name, hello-world, as your package name.

      Then it asks for the version. To use v1.0.0, which comes as default, press ENTER.

      After that, it asks for a description. There you can add a description of your project, something like: hello world application on Electron.js.

      Next, for the entry point, enter main.js.

      The file invoked at the initial run time of application is known as the entry point. In this case, main.js is the entry point of the package.json file.

      For the remaining questions, accept the defaults with ENTER.

      Note: In this tutorial we’re leaving the author and license empty, but you can use your first and last name as the author depending on your project status. The license of your package specifies how others are permitted to use the application, and any restrictions you’re placing on it. The most common licenses are: MIT, BSD-2-Clause, and ISC. For more details, check the full list of SPDX license IDs. From there you can use a preferred license for your project, but this is not mandatory.

      Having followed the prompts you’ll receive the following output:

      Output:

      . . . Use `npm install <pkg>` afterwards to install a package and save it as a dependency in the package.json file. Press ^C at any time to quit. package name: (hello-world) version: (1.0.0) description: hello world application on Electron.js entry point: (index.js) main.js test command: git repository: keywords: author: license: (ISC)

      After that, you’ll be asked to confirm the configuration:

      Output:

      About to write to /hello-world/package.json: { "name": "hello-world", "version": "1.0.0", "description": "hello world application on Electron.js", "main": "main.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "author": "", "license": "ISC" } Is this OK? (yes)

      You’ll now have the newly generated package.json file inside your hello-world project directory. Next you’ll install Electron.

      Step 2 — Installing Electron

      Now you’ll implement the configuration of the package.json file and install Electron.

      For that, open the package.json file in your preferred text editor:

      Add the following highlighted line inside the scripts object:

      package.json

      {
        "name": "hello-world",  "version": "1.0.0",
        "description": "hello world application on Electron.js",
        "main": "main.js",
        "scripts": {
          "start": "electron .",
          "test": "echo "Error: no test specified" && exit 1"
       },
        "author": "",
        "license": "ISC"
      }
      

      The scripts property takes an object with as many key/value pairs as desired. Each one of the keys in these key/value pairs is the name of a command that can be run. The corresponding value of each key is the actual command that can be run. Scripts are frequently used for testing, building, and streamlining of the needed commands.

      In this project, you’ll use start as a key and electron . as a value.

      Once you’re done, save and exit the file.

      Next, you’ll install Electron as a development dependency in your project. Run the following command inside your hello-world project directory:

      • npm install --save-dev electron

      After successfully installing the Electron dependency to your project, the package.json file will be similar to this:

      package.json

      {
        "name": "hello-world",
        "version": "1.0.0",
        "description": "hello world application on Electron.js",
        "main": "main.js",
        "scripts": {
          "start": "electron .",
          "test": "echo "Error: no test specified" && exit 1"
        },
        "author": "",
        "license": "ISC",
        "devDependencies": {
          "electron": "^8.2.1"
        }
      }
      

      The dependency property takes an object that has the name and version for each dependency.

      There are two dependency properties dependencies and devDependencies that can be identified with a key difference. The dependencies property is used to define the dependencies that a module needs to run in production. The devDependencies property is usually used to define the dependencies the module needs to run in development. To install the package as devDependencies use the --save-dev flag with your command.

      You’ve installed Electron to your machine and created the project folder to build your application. Now you’ll write your first hello-world application using the Electron framework.

      Step 3 — Writing the “Hello World!” Application

      Let’s start writing your first Electron application.

      Electron operates with two types of processes: the main process (server-side) and the renderer process (client-side). The Electron main process is run on the Node.js runtime.

      For that, you’ll be working with two files: main.js and index.html.

      main.js is your application’s main process and index.html is your Electron application renderer process.

      hello-world
      +-- package.json
      +-- main.js
      +-- index.html
      

      Next, we create a manual browser window and load the content using Electron API calls, which you can use to execute HTML, CSS, JavaScript, and so on.

      First open your main.js file:

      Then add the following line of code to implement the BrowserWindow module:

      main.js

      const { app, BrowserWindow } = require('electron')
      

      This contains two destructuring assignments called app and BrowserWindow, which are required for an Electron module. The Browserwindow module is used to create a new window in your Electron application.

      Next, add the following code to your main.js file:

      main.js

      . . .
      function createWindow () {
        const mainWindow = new BrowserWindow({
          width: 800,
          height: 600
        })
      
        mainWindow.loadFile('index.html')
      }
      
      app.whenReady().then(createWindow)
      

      You add an Electron createWindow function to your hello-world application. In this function, you create a new BrowserWindow renderer process and pass the width and height parameters. The width and height will set the application window size.

      The mainWindow.loadFile() method renders some contents into the BrowserWindow. The index.html file will load the content.

      The main process will be started when the app.whenReady().then(windowName) method is ready. At this point, the main process calls the createWindow function. This function creates a new renderer process, or browser window instance, with a width of 800px and height of 600px. Then the renderer process proceeds to load content using mainWindow.loadFile('index.html') method. In this tutorial, you use index.html as the filename.

      Next add the following events code to your file:

      main.js

      . . .
      app.on('window-all-closed', () => {
        if (process.platform !== 'darwin') {
          app.quit()
        }
      })
      
      app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0) {
          createWindow()
        }
      })
      

      You add the two main system events into the project—window-all-closed and activate events:

      • window-all-closed: Quits the application when all windows are closed. On macOS it is common for applications and their menu bar to stay active until the user quits explicitly with CMD+Q.
      • activate: Various actions can trigger this event, such as launching the application for the first time, attempting to re-launch the application when it’s already running, or clicking on the application’s dock (macOS) or taskbar icon.

      After adding these code blocks, your final output of the main.js file will be similar to this:

      main.js

      const { app, BrowserWindow } = require('electron')
      
      function createWindow () {
        const mainWindow = new BrowserWindow({
          width: 800,
          height: 600
        })
      
        mainWindow.loadFile('index.html')
      
      }
      
      app.whenReady().then(createWindow)
      
      app.on('window-all-closed', () => {
        if (process.platform !== 'darwin') {
          app.quit()
        }
      })
      
      app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0) {
          createWindow()
        }
      })
      

      Once you’re done, save and exit this file.

      Next, create and open the index.html file:

      Add the following code, which is sent as the final output:

      index.html

      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="UTF-8">
          <title>Hello World!</title>
        </head>
        <body
          <h1>Hello World!</h1>
        </body>
      </html>
      

      Here you create a static HTML web page. The Electron application renderer process supports all HTML syntax since Electron uses Chromium for the rendering process.

      Now that you’re done, you can run your application:

      You will get an application window as an output.

      Hello world printed output window of the application

      You’ve created your first cross-platform application with the Electron framework. Next you’ll work with some customizations, which you can add for interactivity.

      Step 4 — Customizing Your “Hello World!”“ Application

      Now you have completed the initial setup of your first cross-platform application using the Electron framework. Let’s see what else you can do to improve the native behavior of the application.

      Electron has a number of built-in features such as dialog boxes, windows options, new windows, menus, shortcuts, notifications, touch bars, session controls, and so on, that improve the user experience of your desktop application. Let’s add some features to customize the hello-world application.

      Graceful Window Setup of the Application

      When you load a page into the window directly, at the startup of your application you may see the page does not load at once. This isn’t a great experience in native applications. Let’s fix this issue in a few steps.

      To do this, you need to hide the BrowserWindow by passing new configuration parameters at the time it gets created.

      For that, open the main.js file:

      Add the show: false parameter to the body of the BrowserWindow object:

      main.js

      const mainWindow = new BrowserWindow({
         width: 800,
         height: 600,
         show: false
       })
      

      Next, you’ll add a new listener to the BrowserWindow instance by adding the highlighted code line inside the createWindow function body. You’ll also add new configuration parameters into the BrowserWindow to change the background color of the initially built window.

      For that, you have to add the following code line of backgroundColor object, inside the BrowserWindow function. Feel free to change the hex color code as you wish.

      backgroundColor: '#Your hex color code'
      

      Add this line like the following highlighted code to your createWindow function:

      main.js

      function createWindow () {
       const mainWindow = new BrowserWindow({
         width: 800,
         height: 600,
         show: false,
         backgroundColor: '#ffff00'
       })
       mainWindow.loadFile('index.html')
      
       mainWindow.once('ready-to-show', mainWindow.show)
      
      }
      

      To reduce the garbage collection, you need to execute this listener one time by using the once keyword. Therefore, the mainWindow.show method executes only once at the run time of this application.

      Now save your file and run your application using the terminal:

      Your application will show with a yellow background.

      Hello world printed output window with the background color of yellow

      Finally, you will see the application window loading gracefully.

      Creating a New Window for the Application

      The use of more than one window is a common feature of basic to advanced applications. Let’s add that feature to your newly created application.

      Electron can create multiple renderer processes (multiple windows) from a single main process.

      First, open main.js:

      Create a new method called secWindow and set the width and height parameters of the newly created window by adding the highlighted code:

      main.js

      function createWindow () {
      const mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        show: false,
        backgroundColor: '#ffff00'
      })
      
      const secWindow = new BrowserWindow({
        width: 600,
        height: 400,
       })
      . . .
      }
      

      Now load content to the newly created BrowserWindow renderer process. At this time you’ll load some remote URL (Website) content.

      In this tutorial, you’re using https://www.digitalocean.com web content for the second window of the application. For that, in the second window initialization secWindow.loadURL, you add the following code line to the body of the createWindow function:

      main.js

       function createWindow () {
       const mainWindow = new BrowserWindow({
         width: 800,
         height: 600,
         show: false,
         backgroundColor: '#ffff00'
       })
      
      const secWindow = new BrowserWindow({
         width: 600,
         height: 400,
       })
      
       mainWindow.loadFile('index.html')
      
       secWindow.loadURL('https://www.digitalocean.com/')
      
       mainWindow.once('ready-to-show', mainWindow.show)
      }
      

      Now save and exit your file and run your application in the terminal:

      You will get your initial window with the yellow background and a new application with the loaded URL.

      New desktop application window of loaded URL with previously hello world printed output window

      You’ve made customizations to your newly created application to make it more interactive for users. Now it’s time to build your Electron application.

      Step 5 — Building Your First Application

      After adding some features to your application, you need to build it for the purpose of distribution. In this section, you will learn how to build the application for various platforms.

      The build process of the Electron application is considered somewhat hard because it needs a lot of tools. However, here you’ll use the electron-builder CLI tool that provides the best way to build your application for any platform.

      First, you’ll install the electron-builder CLI tools globally. To do this run the following command:

      • sudo npm install -g electron-builder

      Note: You can use either npm or yarn to install electron-builder—there are no noted differences in performance. If you intend to develop your application in the long term, the makers of electron-builder recommend yarn to avoid potential compatibility issues. To install using yarn, you’ll need to ensure it’s installed on your computer and then run yarn add electron-builder --dev to install electron-builder with yarn.

      After completing the installation of the electron-builder, you can verify the success of it by running the following command in your terminal:

      • electron-builder --version

      You’ll receive the current version of Electron in your output.

      Now you can build your first cross-platform application. To do this open your terminal and run the following command in your project directory:

      You use the flags -mwl to build applications for macOS, Windows, and Linux respectively.

      Note: Only macOS users can build for all platforms. Windows users can build the application for Windows and Linux platforms only. Linux users can build only for Linux platforms. For more details, you can refer to the documentation.

      To build applications for separate operating systems use the following:

      Build applications for macOS:

      Build applications for Windows:

      Build applications for Linux:

      This process takes some time while dependencies download and your application builds.

      Your project directory creates a new folder called dist. All your built applications and unzip versions of the application are located in that folder.

      As an example, if you build your application for all platforms, your project dist folder is similar to the following file structure:

      hello-world
        +-- hello-world-1.0.0-mac.zip
        +-- hello-world-1.0.0.dmg
        +-- hello-world Setup 1.0.0.exe
        +-- win-unpacked
        +-- mac
        +-- linux-unpacked
        +-- hello-world_1.0.0_amd64.snap
      

      electron-builder builds the Electron app for the current platform and current architecture as the default target.

      • macOS: DMG and ZIP for Squirrel.Mac
      • Windows: NSIS (.exe)
      • Linux: If you build on Windows or macOS, Snap and AppImage for x64 will be the output. Otherwise if you build on Linux, the output will be Snap and AppImage files for the current architecture.

      You’ve now built your application for all platforms.

      Conclusion

      In this tutorial, you created your first cross-platform application with the Electron framework, added native features, and built it for distribution, on macOS.

      To learn more about Electron, you can check out their documentation. Now you can also share your newly created desktop application with anyone by creating an installer.



      Source link

      How To Set Up a JupyterLab Environment on Ubuntu 18.04


      The author selected the United Nations Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      JupyterLab is a highly feature-rich UI that makes it easy for users, particularly in the fields of Data Science and AI, to perform their tasks. The JupyterLab environments provide a productivity-focused redesign of Jupyter Notebook. It introduces tools such as a built-in HTML viewer and CSV viewer along with features that unify several discrete features of Jupyter Notebooks onto the same screen.

      In this tutorial, you’ll install and set up JupyterLab on your Ubuntu 18.04 server. You’ll also be configuring your server to be able to connect to the JupyterLab instance remotely from any web browser, securely, using a domain name.

      Prerequisites

      In order to complete this tutorial, you’ll need:

      • An Ubuntu 18.04 server with a non-root user account with sudo privileges using this Initial Server Setup Guide.
      • An installation of the Python Anaconda Distribution on your server. You can use the How To Install the Anaconda Python Distribution on Ubuntu 18.04 tutorial.
      • A registered domain name or sub-domain where you’ve access to edit DNS records. This tutorial will use your_domain throughout. You can purchase domains on Namecheap, get a free domain at Freenom, or register a new domain with any registrar of your choice.
      • The following DNS records set up for your domain:
        • An A record with your_domain pointing to your server’s public IP address.
        • An A record with www.your_domain pointing to your server’s public IP address.
          This How to Create, Edit, and Delete DNS Records documentation can help you in setting up these records.

      Step 1 — Setting Up Your Password

      In this step you’ll set up a password on your JupyterLab installation. It is important to have a password in place since your instance will be publicly accessible.

      First, make sure your Anaconda environment is activated. As per the prerequisite tutorial the environment is called base.

      To activate the environment, use the following command:

      Your prompt will change in the terminal to reflect the default Anaconda environment base:

      All future commands in this tutorial will be run within the base environment.

      With your Anaconda environment activated, you’re ready to set up a password for JupyterLab on your server.

      First, let’s generate a configuration file for Jupyter:

      • jupyter notebook --generate-config

      You’ll receive the following output:

      Output

      Writing default config to: /home/sammy/.jupyter/jupyter_notebook_config.py

      Both JupyterLab and Jupyter Notebook share the same configuration file.

      Now, use the following command to set a password for accessing your JupyterLab instance remotely:

      • jupyter notebook password

      Jupyter will prompt you to provide a password of your choice:

      Output

      Enter password: Verify password: [NotebookPasswordApp] Wrote hashed password to /home/sammy/.jupyter/jupyter_notebook_config.json

      Jupyter stores the password in a hashed format at /home/sammy/.jupyter/jupyter_notebook_config.json. You’ll need this hashed value in the future.

      Finally, use the cat command on the file generated by the previous command to view the hashed password:

      • cat /home/sammy/.jupyter/jupyter_notebook_config.json

      You’ll receive an output similar to the following:

      /home/sammy/.jupyter/jupyter_notebook_config.json

      {
        "NotebookApp": {
          "password": "sha1:your_hashed_password"
        }
      }
      

      Copy the value in the password key of the JSON and store it temporarily.

      You’ve set up a password for your JupyterLab instance. In the next step you’ll create a Let’s Encrypt certificate for your server.

      Step 2 — Configuring Let’s Encrypt

      In this step, you’ll create a Let’s Encrypt certificate for your domain. This will secure your data when you access your JupyterLab environment from your browser.

      First, you’ll install Certbot to your server. Begin by adding its repository to the apt sources:

      • sudo add-apt-repository ppa:certbot/certbot

      On executing the command, you’ll be asked to press ENTER to complete adding the PPA:

      Output

      This is the PPA for packages prepared by Debian Let's Encrypt Team and backported for Ubuntu. Note: Packages are only provided for currently supported Ubuntu releases. More info: https://launchpad.net/~certbot/+archive/ubuntu/certbot Press [ENTER] to continue or Ctrl-c to cancel adding it.

      Press ENTER to continue adding the PPA.

      Once the command has finished executing, refresh the apt sources using the apt update command:

      Next, you’ll install Certbot:

      Before you can start running Certbot to generate certificates for your instance, you’ll allow access on port :80 and port :443 of your server, so that Certbot can use these ports to verify your domain name. Port :80 is checked for http requests to the server while port :443 is used for https requests. Certbot shall be making an http request first and then after obtaining the certficates for your server, it will make an https request, which will be proxied through port :443 to the process listening at :80 port. This will verify the success of your certificate installation.

      First, allow access to port :80:

      You will receive the following output:

      Output

      Rule added Rule added (v6)

      Next, allow access to port :443:

      Output

      Rule added Rule added (v6)

      Finally, run Certbot to generate certificates for your instance using the following command:

      • sudo certbot certonly --standalone

      The standalone flag directs certbot to run a temporary server for the duration of the verfication process.

      It will prompt you for your email:

      Output

      Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator standalone, Installer None Enter email address (used for urgent renewal and security notices) (Enter 'c' to cancel): your_email

      Enter a working email and press ENTER.

      Next, it will ask you to review and agree to the Terms of Service for Certbot and Let’s Encrypt. Review the terms, type A if you accept, and press ENTER:

      Output

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must agree in order to register with the ACME server at https://acme-v02.api.letsencrypt.org/directory - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (A)gree/(C)ancel: A

      It will now prompt you to share your email with the Electronic Frontier Foundation. Type your answer and press ENTER:

      Output

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Would you be willing to share your email address with the Electronic Frontier Foundation, a founding partner of the Let's Encrypt project and the non-profit organization that develops Certbot? We'd like to send you email about our work encrypting the web, EFF news, campaigns, and ways to support digital freedom. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (Y)es/(N)o: Y/N

      Finally, you’ll be asked to enter your domain name. Type in your domain name without any protocol specification:

      Output

      Please enter in your domain name(s) (comma and/or space separated) (Enter 'c' to cancel): your_domain Obtaining a new certificate Performing the following challenges: http-01 challenge for your_domain Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/your_domain/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/your_domain/privkey.pem Your cert will expire on 2020-09-28. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal. - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le

      Certbot will perform domain verification and generate certificates and keys for your domain and store them at /etc/letsencrypt/live/your_domain.

      Now that you have set up your Let’s Encrypt certificate, you’ll update your JupyterLab configuration file.

      Step 3 — Configuring JupyterLab

      In this step, you will edit the JupyterLab configuration to make sure it uses the Let’s Encrypt certificate you generated in Step 2. You will also make it accessible using the password you set up in Step 1.

      First, you need to edit the JupyterLab configuration file at /home/sammy/.jupyter/jupyter_notebook_config.py:

      • nano /home/sammy/.jupyter/jupyter_notebook_config.py

      Now, navigate to the line defining the value for c.NotebookApp.certfile and update it as follows:

      /home/sammy/.jupyter/jupyter_notebook_config.py

      ...
      ## The full path to an SSL/TLS certificate file.
      c.NotebookApp.certfile="/etc/letsencrypt/live/your_domain/fullchain.pem"
      ...
      

      Next, find the c.NotebookApp.keyfile variable and set it as shown:

      /home/sammy/.jupyter/jupyter_notebook_config.py

      ...
      ## The full path to a private key file for usage with SSL/TLS.
      c.NotebookApp.keyfile="/etc/letsencrypt/live/your_domain/privkey.pem"
      ...
      

      c.NotebookApp.certfile and c.NotebookApp.keyfile refer to the SSL Certificate, which will be served when you try to access your server remotely using the https protocol.

      Next, navigate to the line defining the c.NotebookApp.ip variable and update as follows:

      /home/sammy/.jupyter/jupyter_notebook_config.py

      ...
      ## The IP address the notebook server will listen on.
      c.NotebookApp.ip = '*'
      ...
      

      c.NotebookApp.ip defines the IPs that can access JupyterLab running your server. You set it to the * wildcard to allow access from any computer you need to access JupyterLab on.

      Next, find the c.NotebookApp.open_browser configuration and update it as follows:

      /home/sammy/.jupyter/jupyter_notebook_config.py

      ...
      ## Whether to open in a browser after starting. The specific browser used is
      #  platform dependent and determined by the python standard library `webbrowser`
      #  module, unless it is overridden using the --browser (NotebookApp.browser)
      #  configuration option.
      c.NotebookApp.open_browser = False
      ...
      

      By default, JupyterLab attempts to automatically initiate a browser session when it starts running. Since we do not have a browser on the remote server, it is necessary to turn this off to avoid errors.

      Next, navigate down to the c.NotebookApp.password variable and change to the following:

      /home/sammy/.jupyter/jupyter_notebook_config.py

      ...
      ## Hashed password to use for web authentication.
      #
      #  To generate, type in a python/IPython shell:
      #
      #    from notebook.auth import passwd; passwd()
      #
      #  The string should be of the form type:salt:hashed-password.
      c.NotebookApp.password = 'your_hashed_password'
      ...
      

      JupyterLab will use this hashed password configuration to check the password you enter for access in your browser.

      Finally, navigate further through the file and update the entry of the c.NotebookApp.port:

      /home/sammy/.jupyter/jupyter_notebook_config.py

      ...
      ## The port the notebook server will listen on.
      c.NotebookApp.port = 9000
      ...
      

      c.NotebookApp.port sets a fixed port for accessing your JupyterLab runtime. This way, you can allow access for only one port from the ufw firewall.

      Once you’re done, save and exit the file.

      Finally, allow traffic on the 9000 port:

      You’ll receive the following output:

      Output

      Rule added Rule added (v6)

      Now that you’ve set all your configuration, you’ll run JupyterLab.

      Step 4 — Running JupyterLab

      In this step, you’ll perform a test run of the JupyterLab instance.

      First, change your current working directory to the user’s home directory:

      Now, modify the access permissions of the certificate files to allow JupyterLab to access them. Change the permissions of the /etc/letsencrypt folder to the following:

      • sudo chmod 750 -R /etc/letsencrypt
      • sudo chown sammy:sammy -R /etc/letsencrypt

      Then, invoke your JupyterLab instance to start using the following command:

      This command accepts several configuration parameters. However, since we have already made these changes in the configuration file, we do not need to provide them here explicitly. You can provide them as arguments to this command to override the values in the configuration file.

      You can now navigate to https://your_domain:9000 to check you receive JupyterLab’s login screen.

      If you log in with the password you set up for JupyterLab in Step 2, you’ll be presented with the JupyterLab interface.

      JupyterLab interface after login

      Finally, press CTRL+C twice to stop the JupyterLab server.

      In the next step, you’ll set up a system service so that the JupyterLab server can be run in the background continuously.

      Step 6 — Setting Up a systemd Service

      In this step, you will create a systemd service that allows JupyterLab to keep running even when the terminal window is exited. You can read more about systemd services and units in this guide on systemd essentials.

      First, you’ll have to create a .service file, using the following command:

      • sudo nano /etc/systemd/system/jupyterlab.service

      Add the following content to the /etc/systemd/system/jupyterlab.service file:

      /etc/systemd/system/jupyterlab.service

      [Unit]
      Description=Jupyter Lab Server
      
      [Service]
      User=sammy
      Group=sammy
      Type=simple
      WorkingDirectory=/home/sammy/
      ExecStart=/home/sammy/anaconda3/bin/jupyter-lab --config=/home/sammy/.jupyter/jupyter_notebook_config.py
      StandardOutput=null
      Restart=always
      RestartSec=10
      
      [Install]
      WantedBy=multi-user.target
      

      Save and exit the editor once you’re done.

      The service file automatically registers itself in the system as a daemon. However, it is not running by default.

      Use the systemctl command to start the service:

      • sudo systemctl start jupyterlab

      This starts the JupyterLab server in the background. You can check if the server has started by using the following command:

      • sudo systemctl status jupyterlab

      You’ll receive the following output:

      Output

      ● jupyterlab.service - Jupyter Lab Server Loaded: loaded (/etc/systemd/system/jupyterlab.service; disabled; vendor preset: enabled) Active: active (running) since Sun 2020-04-26 20:58:29 UTC; 5s ago Main PID: 5654 (jupyter-lab) Tasks: 1 (limit: 1152) CGroup: /system.slice/jupyterlab.service └─5654 /home/sammy/anaconda3/bin/python3.7 /home/sammy/anaconda3/bin/jupyter-lab --config=/home/

      Press Q to exit the service status output.

      You can now head to https://your_domain:9000 in any browser of your choice, provide the password you set up in Step 2, and access the JupyterLab environment running on your server.

      Step 7 — Configuring Renewal of Your Let’s Encrypt Certificate

      In this final step, you will configure your SSL certificates provided by Let’s Encrypt to automatically renew when they expire every 90 days and then restart the server to load the new certificates.

      While Certbot takes care to renew the certificates for your installation, it does not automatically restart the server. To configure the server to restart with new certificates, you will have to provide a renew_hook to the Certbot configuration for your server.

      You’ll need to edit the /etc/letsencrypt/renewal/your_domain.conf file and add a renew_hook to the end of the configuration file.

      First, use the following command to open the /etc/letsencrypt/renewal/your_domain.conf file in an editor:

      • sudo nano /etc/letsencrypt/renewal/your_domain.conf

      Then, at the bottom of this file, add the following line:

      /etc/letsencrypt/renewal/your_domain.conf

      ...
      renew_hook = systemctl reload jupyterlab
      

      Save and exit the file.

      Finally, run a dry-run of the renewal process to verify that your configuration file is valid:

      • sudo certbot renew --dry-run

      If the command runs without any error, your Certbot renewal has been set up successfully and will automatically renew and restart your server when the certificate is near the date of expiry.

      Conclusion

      In this article, you set up a JupyterLab environment on your server and made it accessible remotely. Now you can access your machine learning or data science projects from any browser and rest assured that all exchanges are happening with SSL encryption in place. Along with that, your environment has all the benefits of cloud-based servers.



      Source link