Explore Parcel - A Webpack Alternative For Small Projects





Good day, friends!



The main purpose of a module builder or bundler like Webpack or Parcel is to ensure that all the modules required for the application to run are included in the correct order in one minified (for production build) script that is included in index. html.



In fact, builders, as a rule, can optimize not only JS, but also HTML, CSS files, can convert Less, Sass to CSS, TypeScript, React and Vue (JSX) to JavaScript, work with images, audio, video and others data formats, and also provide additional features, such as: creating a map of (used) resources or sources (source map), visual representation of the size of the entire bundle and its individual parts (modules, libraries), dividing the code into parts (chunks), including number, for reuse purposes (for example, libraries that are used in several modules are taken out in a separate file and loaded only once), smart loading of packages from npm (for example, loading only Russian localization from moment.js), all kinds of plugins for solving specific tasks etc.



In this respect, the leadership, of course, belongs to Webpack. However, what if we are developing a project in which most of the functionality provided by this wonderful tool is not needed? Are there alternatives to this technology that are easier to learn and use? For me, the answer to this question was Parcel . By the way, if you are interested in learning about Webpack, I recommend watching this video . My file with Webpack settings for this tutorial is located here .



















With your permission, I will not retell the documentation in my own words, especially since it is available in Russian, but will focus on the practical component, namely: using template strings and dynamic import, we will create a SPA consisting of three pages in JavaScript, style the application with CSS, write a simple function in TypeScript, import it into the application, style the container for the results of this function using Sass, and build the application using Parcel in both development and production modes.



The project code is here .



If you are interested, please follow me.



application



Ready? Then let's go.



Create the parcel-tutorial directory.



We go into it and initialize the project using npm init -y.



Create the index.html file. We'll be using one of the standard Bootstrap Cover templates:



<head>
    ...
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
</head>

<!-- Bootstrap class -->
<body class="text-center">

    <! -- Main script -->
    <script src="index.js"></script>
</body>


Go to the official Bootstrap website , go to the Examples section, find the Cover in Custom components, press Ctrl + U (Cmd + U) to view the page code.











Create the src directory, and there are two more folders in it - js and css.



Create the following files in the js directory: header.js, footer.js, home.js, projects.js and contact.js. These are modules or, if you like, components of our application: header, footer, content of the main page and other pages.



In the css directory, create a style.css file.



At the moment, the project structure looks like this:



-- parcel-tutorial
    -- src
        -- css
            -- style.css
        -- js
            -- contact.js
            -- footer.js
            -- header.js
            -- home.js
            -- projects.js
    -- index.html
    -- index.js
    -- package.json


Back to Bootstrap.



Copy-paste the page code into the corresponding modules with minor changes.



header.js:



export default `
  <header class="masthead mb-auto">
      <div class="inner">
          <h3 class="masthead-brand">Parcel Tutorial</h3>
          <nav class="nav nav-masthead justify-content-center">
            <a class="nav-link active" name="home">Home</a>
            <a class="nav-link" name="projects">Projects</a>
            <a class="nav-link" name="contact">Contact</a>
        </nav>
      </div>
  </header>
`.trim()


Please note that we have changed the href to name in the links.



footer.js:



export default `
  <footer class="mastfoot mt-auto">
    <div class="inner">
      <p>Β© 2020. All rights reserved.</p>
    </div>
  </footer>
`.trim()


home.js:



export default `
  <h1 class="cover-heading">Home page</h1>
  <p class="lead">Home page content</p>
`.trim()


projects.js:



export default `
  <h1 class="cover-heading">Projects page</h1>
  <p class="lead">Projects page content</p>
`.trim()


contact.js:



export default `
  <h1 class="cover-heading">Contact page</h1>
  <p class="lead">Contact page content</p>
`.trim()


Don't forget to copy the styles from cover.css to style.css.



Open up index.js.



Let's import the header, footer and styles:



import header from './src/js/header.js'
import footer from './src/js/footer.js'
import './src/css/style.css'


The content of the main and other pages will be loaded dynamically when the link is clicked, so we create an object like this:



const pages = {
    home: import('./src/js/home.js'),
    projects: import('./src/js/projects.js'),
    contact: import('./src/js/contact.js')
}


The property name of this object is the corresponding (requested by the user) page.



We generate the start page using the previously imported header and footer components:



// Bootstrap classes
document.body.innerHTML = `
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
    ${header}
    <main role="main" class="inner cover"></main>
    ${footer}
</div>
`.trim()


The content of the pages will be displayed in the main element, so we define it:



const mainEl = document.querySelector('main')


Create a page rendering function:



const renderPage = async name => {
    const template = await pages[name]
    mainEl.innerHTML = template.default
}


We need to wait for the corresponding module to load, so we use async / await (the await keyword pauses the execution of the function). The function takes the name of the requested page (name) and uses it to access the corresponding property of the pages object (pages [name]). We then insert the resulting template into mainEl. In fact, await returns a Module object that contains the template. Therefore, when inserting a template as markup in mainEl, it is necessary to refer to the default property of the Module object (modules are exported by default), otherwise, we will receive an error - it is impossible to convert the object to HTML.



Render the main page:



renderPage('home')


The active link corresponding to the current page has the active class. We need to switch classes when rendering a new page. Let's implement the helper function:



const toggleClass = (activeLink, currentLink) => {
    if (activeLink === currentLink) {
        return;
    } else {
        activeLink.classList.remove('active')
        currentLink.classList.add('active')
    }
}


The function takes two arguments - a link with the class active (activeLink) and the link that was clicked (currentLink). If the specified links match, we do nothing. Otherwise, we change the classes.



Finally, we need to add a link click handler. Let's implement one more helper function:



const initClickHandlers = () => {
    const navEl = document.querySelector('nav')

    navEl.addEventListener('click', ev => {
        if (ev.target.tagName === 'A') {
            const activeLink = navEl.querySelector('.active')
            const currentLink = ev.target
            toggleClass(activeLink, currentLink)
            renderPage(currentLink.name)
        }
    })
}


In this function, we first find the nav element. Then, through delegation, we process the clicks on the links: if the target element is the A tag, we get the active link (link with the active class), the current link (the link that was clicked), change the classes and render the page. The value of the name attribute of the current link is passed as the renderPage argument.



We're almost done with the app. However, before proceeding to building a project using Parcel, it is necessary to note the following: today, support for dynamic modules according to Can I use data is 90%. That's a lot, but we're not ready to lose 10% of our users. Therefore, our code needs to be converted to less modern syntax. Babel is used for transpilation. We need to connect two additional modules:



import "core-js/stable";
import "regenerator-runtime/runtime";


Please note that we do not install these packages with npm.



Also, let's immediately implement a function in TypeScript, something very simple, like a function for adding two numbers.



Create an index.ts file in the js directory with the following content:



export const sum = (a: number, b: number): number => a + b


The only difference from JavaScript, apart from the file extension (.ts), is that we explicitly specify the types of values ​​accepted and returned by the function - in this case, number. In fact, we could limit ourselves to defining the return type, TypeScript is smart enough to know that if the return value is a number, then the arguments passed must be numbers. Never mind.



Let's import this function into index.js:



import { sum } from './src/js/index.ts'


And call it with arguments 1 and 2 in renderPage:



const renderPage = async name => {
    // ...
    mainEl.insertAdjacentHTML('beforeend', `
        <output>Result of 1 + 2 -> <span>${sum(1, 2)}<span></output>
    `)
}


Styling the function result container using Sass. In the css folder, create a style.scss file with the following content:



$color: #8e8e8e;

output {
    color: $color;
    border: 1px solid $color;
    border-radius: 4px;
    padding: .5rem;
    user-select: none;
    transition: transform .2s;

    & span {
        color: #eee;
    }

    &:hover {
        transform: scale(1.1);
    }
}


Let's import these styles into index.js:



import './src/css/style.scss'


Note again that we are not installing TypeScript and Sass with npm.



We're done with the app. Moving on to Parcel.



Parcel



To install Parcel globally, you need to run the command npm i parcel-bundler -gin the terminal.



Open package.json and set up Parcel launch in development and production modes:



"scripts": {
    "dev": "parcel index.html --no-source-maps --open",
    "pro": "parcel build index.html --no-source-maps --no-cache"
  },


The team npm run devstarts building the project for development, and the team starts npm run profor production. But what do all these flags mean? And why didn't we install Babel, TypeScript and Sass via npm?



The fact is that Parcel automatically installs all dependencies when it detects their import or use in the application. For example, if Parcel sees stylesheets imported from a .scss file, it installs Sass.



Now about the teams and flags.



To build the project in development mode, use the command parcel index.html, where index.html is the entry point of the application, i.e. a file that contains a link to the main script or scripts. Also, this command starts a local server at localhost: 1234.



The flag --no-source-mapsmeans that we don't need resource maps.



Flag--opentells Parcel to open index.html after build in a browser on the local server.



To build a project in production mode, use the command parcel build index.html. This assembly assumes minification of JS, CSS and HTML files.



The flag --no-cachemeans disabling resource caching. Caching provides a high speed of building and rebuilding the project in real time. This is relevant when developing, but not too much when assembling a finished product.



One more point: by default, Parcel places the generated files in the dist folder, which is created if it is missing. The problem is that when rebuilding, old files are not deleted. To remove such files, you need a special plugin, for example parcel-plugin-clean-easy .



Install this plugin usingnpm i parcel-plugin-clean-easy -D and add the following to package.json:



"parcelCleanPaths": [
    "dist",
    ".cache"
  ]


parcelCleanPaths are directories to be removed on rebuild.



Parcel is now fully configured. Open a terminal, type npm run dev, press enter.















Parcel builds the project in development mode, starts the local server, and opens the application in a browser. Excellent.



Now let's try to put together a project for production.



We execute the command npm run pro.











We launch the application in the browser.







Oops, it looks like something went wrong.



Let's take a look at the generated index.html. What do we see there? Hint: Pay attention to the paths in the link and script tags. I don’t know exactly what it’s connected with, but Parcel converts relative links to "/ path-to-file", and the browser does not read such links.



In order to solve this problem, you need to add the "--public-url." Flag to the "pro" script.



We start rebuilding.



The relative paths are correct and the application works. Cool.







That's all for me. Thank you for attention.



All Articles