Tiếp tục bài trước thì hôm nay chúng ta cùng tìm hiểu Webpack thông qua một số setting nâng cao hơn. Để hiểu được bài hôm nay thì các bạn phải nắm được các kiến thức căn bản ở bài trước đã nha ;-). Oke bắt đầu thôi :mrgreen:

Cài đặt Webpack và các style loader

Tạo một project mới và khởi tạo file package.json với câu lệnh npm

npm init

Cài Webpack và các style loader cho css và sass

yarn add webpack webpack-cli webpack-dev-server style-loader css-loader sass sass-loader file-loader -D

Mình sẽ không giải thích gì thêm về lý do tại sao mình lại cài những devDependencies này nữa. Nếu anh em nào không hiểu có thể đọc lại bài trước

Copy các dòng lệnh này vào mục script trong package.json

"start": "webpack serve --mode development", 
"build": "webpack --mode production"

Cài đặt HTMLWebpackPlugin

yarn add html-webpack-plugin -D

Mình sẽ giải thích vì sao cài devDepedency này phía dưới

Cài đặt Typescript

Typescript là ngôn ngữ được xây dựng trên Javascript, giúp bạn có thể viết code giống javascript nhưng có kiểu dữ liệu rõ ràng. Anh em có thể tưởng tượng Typescript giống như SASS vậy, Typescript sẽ được biên dịch sang Javascript cho trình duyệt đọc được.

Vì anh em có thể dùng Typescript để biên dịch sang phiên bản Javascript version phù hợp nên không cần dùng babel ở trong trường hợp này.

yarn add typescript ts-loader -D

Giải thích:

  • typescript: Phần lõi của ngôn ngữ Typescript
  • ts-loader: Giúp tích hợp Typescript vào webpack

Cầu hình từng file

Ta sẽ xây dựng cấu trúc thư mục tương tự bài trước nhưng, chỉ khác một chút là đổi .js sang .ts

Cấu trúc thư mục

Cấu trúc thư mục

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Training Webpack</title>
  </head>
  <body>
    <div id="root">
      <h1>Học Webpack siêu tốc 2</h1>
    </div>
    <noscript> You need to enable JavaScript to run this app. </noscript>
  </body>
</html>

Nếu mọi người để ý thì file html mình không import bất cứ file js nào cả. Lý do thì mình sẽ giải thích phía dưới, mọi người cứ tạo file index.html như vậy trước đã nha.

add.ts

export const add = (a = 1, b = 2) => a + b
export const treeShaking = () => {
  console.log('Dòng này sẽ không có trong file build')
}

app.d.ts

declare module '*.jpg' {
  const src: string
  export default src
}

Giải thích tại sao lại có file này: Vì Typescript bắt type rất chặt chẽ, và các file như file ảnh, css, video thì không được Typescript coi là module, nên sẽ không cho import. Vậy nên bạn phải khai báo type cho mỗi  loại file.

index.scss

$color: #ddd;
#root {
  text-align: center;
  background-color: $color;
  padding: 100px;
}

loadImage.ts

import logo from './logo.jpg'

const component = () => {
  const element = document.createElement('div')
  const webpackLogo = new Image()
  webpackLogo.src = logo
  webpackLogo.width = 200
  element.appendChild(webpackLogo)
  return element
}

document.getElementById('root')?.appendChild(component())

subtract.ts

export const subtract = (a, b) => a - b

index.ts

import { subtract } from './subtract'
import { add } from '@@/src/add'
import './loadImage'
import './index.scss'

console.log(`1 + 2 = ${add(1, 2)}`)
console.log(`8 - 2 = ${subtract(8, 2)}`)

Nhớ thêm 1 ảnh logo.jpg vào thư mục src nha

tsconfig.json

{
  "compilerOptions": {
    "target": "ES5",
    "allowJs": true,
    "strict": true,
    "module": "ESNext",
    "moduleResolution": "node",
    "noImplicitAny": false,
    "sourceMap": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@@/*": ["./*"]
    }
  },
  "include": ["src/**/*"]
}

Giải thích: Muốn chi tiết hơn thì anh em có thể xem tại đây

  • target: là version javascript khi được build
  • allowJs: Cho phép dùng file js trong project Typescript
  • strict: chế độ strict mode cho Typescript
  • module: sau khi biên dịch ra mã javascript thì mã này được viết dưới dạng module ESNext. Riêng option này có rất nhiều tùy chọn, khuyên anh em không nên chọn CommonJs, vì nó sẽ làm mất đi tính năng Tree-Shaking của webpack (anh em nào chưa rõ tree-shaking thì đọc lại bài trước giúp mình nha).
  • noImplicitAny: Không cho phép ngầm hiểu any
  • sourceMap: cho phép hiện souremap TS ( về vấn đề sourcemap với TS thì bạn phải mở trong tsconfig.json và trong webpack mới đầy đủ nhé, thiếu một cái là sẽ không còn chính xác đâu)
  • baseUrl: đường dẫn cơ sở, thường là “.”. Nếu bạn dùng option paths dưới đây thì phải quy định baseUrl
  • paths: tạo alias để thuận tiện việc import. Ví dụ thay vì bạn dùng ../../../ thì bây giờ bạn có thể rút ngắn lại thành @/. Việc cấu hình alias ở tsconfig.json chỉ giúp editor code hiểu, nó không có tác dụng với webpack. Vì thế bạn phải cấu hình với alias với webpack phía dưới nữa.
  • include: quy định các file được sử dụng trong chương trình.

webpack.config.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = (env, agrv) => {
  const isDev = agrv.mode === 'development'
  return {
    entry: './src/index.ts',
    module: {
      rules: [
        {
          test: /\.(ts|tsx)$/,
          use: 'ts-loader',
          exclude: /node_modules/
        },
        {
          test: /\.(s[ac]ss|css)$/,
          use: [
            'style-loader',
            {
              loader: 'css-loader',
              options: { sourceMap: isDev ? true : false }
            },
            {
              loader: 'sass-loader',
              options: { sourceMap: isDev ? true : false }
            }
          ]
        },
        {
          test: /\.(png|svg|jpg|gif)$/,
          use: [
            {
              loader: 'file-loader',
              options: {
                name: '[path][name].[ext]'
              }
            }
          ]
        }
      ]
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.js', '.jsx'],
      alias: {
        '@': path.resolve('src'),
        '@@': path.resolve()
      }
    },
    output: {
      path: path.resolve('dist'),
      publicPath: '',
      filename: 'bundle.[hash:6].js',
      environment: {
        arrowFunction: false,
        bigIntLiteral: false,
        const: false,
        destructuring: false,
        dynamicImport: false,
        forOf: false,
        module: false
      }
    },
    devtool: isDev ? 'source-map' : false,
    devServer: {
      contentBase: 'public',
      port: 3000,
      hot: true,
      watchContentBase: true
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: 'public/index.html'
      })
    ]
  }
}

Giải thích:

entry: File đầu vào cho webpack, file này thường là file import mọi file khác

module: Chứa các loader của webpack

resolve.extensions: Thứ tự ưu tiên các file khi import

alias: Tạo alias thuận tiện cho việc import trong webpack

output.path: Đường dẫn thư mục build

output.filename: Tên file bundle sau khi được build. [hast:6] nghĩa là bundle sẽ được thêm 1 đoạn hash 6 ký tự ngẫu nhiên. Việc này nhằm mục đích hạn chế trình duyệt cache Javascript khi bạn update một version Javascript mới cho website của bạn.

Lúc này sẽ nãy ra 1 vấn đề, mỗi lần build thì ta sẽ có 1 file bundle mới, vậy chẳng lẻ ta lại phải vào public/index.html sửa đường dẫn file js hay sao?

Đừng lo, chúng ta sẽ dùng 1 plugin là HtmlWebpackPlugin sẽ giúp chúng ta tạo 1 file html mới từ file html ban đầu. File html mới này sẽ dùng template là file public/index.html và tự động thêm đoạn src là file js mới được build vào.

Vì thế mà plugins ta dùng HtmlWebpackPlugin

output.publicPath: Chứa đường dẫn tương đối mà từ file index.html trỏ đến các file trong thư mục dist sau khi build. Lưu ý là file index.html được build nằm trong thư mục dist.

output.environment: Mặc định webpack generate ra code dùng 1 số cú pháp của ES6, nhưng target mình mong muốn là ES5 nên mình cần chỉnh một số thông số như sau.

  • arrowFunction: Hỗ trợ arrow function.
  • bigIntLiteral: Hỗ trợ BigInt
  • const: Hỗ trợ khai báo const và let
  • destructuring: Hỗ trợ destructuring
  • dynamicImport: Hỗ trợ async import
  • forOf: Hỗ trợ vòng lặp forOf cho các array
  • module: Hỗ trợ moudle ES6 (import … from ‘…’)’

output.devtool: tùy chọn sourcemap

devServer.contentBase: Chứa đường dẫn tương đối đến file index.html

devServer.port: port khi chạy localhost

devServer.hot: Chế độ hot reload. Mặc định thì ở dev server thì webpack sẽ refresh lại trang mỗi khi có thay đổi nhỏ trong code.

devServer.publicPath: Chứa đường dẫn tương đối từ thư mục root trỏ đến thư mục build (ở đây là dist). Chú ý phải thêm / ở trước và sau. Nhưng vì dùng HtmlWebpackPlugin nên ta sẽ tính từ chính thư mục dist. Vì thế giá trị cần dùng là /. Ở đây mình không dùng giá trị nào cả, vì mặc định nó đã là /

devServer.watchContentBase: Nếu bạn có thay đổi gì trong file index.html thì trình duyệt cũng tự động reload.

Để chạy khi dev

yarn start

Để build ra thành phẩm phục vụ deploy

yarn build

Các bạn sẽ thấy xuất hiện thư mục dist và file index.html được build ra trong đó. Chúng ta dùng chính file dist/index.html để chạy nha

Thư mục Dist

Thư mục dist

Tóm lại

Cảm ơn mọi người đã đọc đến đây, bài này khá đơn giản nếu mọi người đã hiểu bài trước . Hẹn gặp lại ở bài Cấu hình Typescript React Webpack hoàn chỉnh nhé :mrgreen:

Github: link github project, folder bài 2 nhé 😀