webpack4 开发环境搭建

1. 概念

本质上,webpack是一个现代JavaScript应用程序的静态模块打包器(module bundler)。当webpack处理应用程序时,会递归地创建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle。

2. 初始化项目

本次环境将使用webpack4来构建配置。

mkdir webpack-demo && cd webpack-demo

npm init -y

npm install webpack webpack-cli --save-dev 

3. 目录改造

项目初始化完成,目录结构如下

w1.png

根目录下建立dist文件夹、src文件夹、webpack.config.js文件和.gitignore文件。

src文件夹下建立scss、images、js子文件夹和index.html文件。
js文件夹和scss文件夹下分别建立index.js和index.scss文件。
images文件夹下放入一个名称为info的图片

webpack4中,现在已经可以无须任何配置,然而大多数项目会需要很复杂的配置,所以webpack仍然要支持配置文件,本次配置也将使用配置文件的方式。

Sass是一款成熟、稳定、强大的专业级css扩展语言,本次项目将使用Sass来编写样式文件。

.gitignore文件是git上传的忽略文件。

改造完目录如下

w2.png

4. 配置webpack.config.js

配置js文件的入口和打包后的文件名称及输出目录。

entry是文件的入口,当前配置使用单页面的方式,可以配置多个入口文件。
output是打包后的输出目录,当前配置是根路径下的dist/js目录。

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    index: path.resolve(__dirname, 'src/js/index.js')
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].js'
  }
}

5. 配置package.json文件

在根目录下直接运行cmd命令 webpack --config webpack.config.js 就可以使用webpack-cli编译项目。考虑到使用CLI方式运行本地的webpack不方便,我们可以编写一个NPM脚本。

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "webpack": "webpack --config webpack.config.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.42.0",
    "webpack-cli": "^3.3.11"
  }
}

执行 npm run webpack 命令后,查看dist目录,如果发现dist目录下已经生成js/index.js文件。说明第一步配置已经完成。

w3.png

6. 编译ES6语法

编译ES6语法需要使用 babel-loader、babel-core、babel-preset-latest和uglifyjs-webpack-plugin。

babel-core:封装babel编译时需要使用的API;
babel-loader:负责ES6语法转化,webpack打包时使用babel-loader处理js文件;
babel-preset-latest:特殊的presets,包括es2015、es2016、… es2017;
uglifyjs-webpack-plugin:用来缩小(压缩优化)js文件;

babel-loader和babel-core需要版本对应,负责会编译报错。
babel-loader 8.x 对应 babel-core 7.x
babel-loader 7.x 对应 babel-core 6.x

依赖安装

npm install babel-core@6.26.3 babel-loader@7.1.5 --save-dev
npm install babel-preset-latest uglifyjs-webpack-plugin --save-dev

依赖配置

webpack.config.js 中增加以下配置

const path = require('path'),
      uglify = require('uglifyjs-webpack-plugin');

module 配置

module: {
  rules: [
    {
      test: /\.js$/,
      loader: 'babel-loader',
      exclude: path.resolve(__dirname, 'node_modules'),
      query: {
        'presets': ['latest']
      }
    }
  ]
}

plugins 配置

plugins: [
  new uglify()
]

运行测试

index.js

const arr = ['张三', '李四', '王五', '赵六'],
      iterator = arr.keys();

for (const key of iterator) {
  document.write(arr[key]);
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>webpack4</title>
</head>
<body>

  <script type="text/javascript" src="../dist/js/index.js"></script>
  
</body>
</html>

index.js中编写测试程序,使用打包命令 npm run webpack打包,并在index.html中引入打包好的js文件。浏览器运行如果打印出循环的arr名称,说明babel配置已经完成。

w4.png

7. 编译.scss文件

编译Sass语法需要使用到 css-loader、style-loader、sass-loader、node-sass、postcss-loader、autoprefixer、extract-text-webpack-plugin@next。

css-loader:通过require的方式引入css,使用css-loader先将css代码编译,然后再使用style-loader插入到网页中;

style-loader:配置css-loader使用,可以以内联样式的形式在html页面中插入css代码;

sass-loader、node-sass:用来解析解析.scss文件,编译sass语法;

postcss-loader:一个用JavaScript工具和插件转换css代码的工具;

autoprefixer:配合postcss-loader为css的属性添加浏览器特定的前缀;

extract-text-webpack-plugin(可选):可以抽离css文件,并以外链的方式引入css文件。

相对于mini-css-extract-plugin,webpack4推荐使用extract-text-webpack-plugin来提取css样式。

依赖安装

npm install css-loader style-loader sass-loader node-sass --save-dev
npm install postcss-loader autoprefixer extract-text-webpack-plugin@next

依赖配置

webpack.config.js 中增加以下配置(二选一)。

const path = require('path'),
      autoprefix = require('autoprefixer'),
      uglify = require('uglifyjs-webpack-plugin'),
      extractTextPlugin = require('extract-text-webpack-plugin');

1. 不提取css文件,内联样式

{
  test: /\.scss$/,
  use: [
    'style-loader',
    'css-loader',
    { 
      loader: 'postcss-loader',
      options: {
        plugins: function () {
          return [autoprefix('last 5 versions')]
        }
      }
    },
    'sass-loader'
  ]
}

2. 提取css文件,外链样式

module 配置

{
  test: /\.scss$/,
  use: extractTextPlugin.extract({
    fallback: 'style-loader',
    use: [
      'css-loader',
      { 
        loader: 'postcss-loader',
        options: {
          plugins: function () {
            return [autoprefix('last 5 versions')]
          }
        }
      },
      'sass-loader'
    ]
  })
}

plugins 配置

plugins: [
  new extractTextPlugin("css/styles.css"),
  new uglify()
]

运行测试

inde.scss

#app {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 200px;
  height: 200px;
  margin: 20px auto;
  border: 1px solid transparent;
  background-color: orange;
  box-shadow: 2px 3px 5px #ddd;
  box-sizing: border-box;

  &:hover {
    border-color: #ddd;
  }
}

index.js

import '../css/index.scss'

const App = (doc) => {
  const app = doc.createElement('div');

  app.id = 'app';

  document.body.appendChild(app);
}

App(document);

编写index.scss,并在index.js文件中引入,编译后运行index.html进行测试。
出现以下效果,说明配置完成。

w5.png

8. 服务器及热更新配置

服务器热更新配置需要使用 webpack-dev-server、html-webpack-plugin。

webpack-dev-server:一个小型的Node.js Express服务器;
html-webpack-plugin:可以根据模板文件动态生成HTML文件;

依赖安装

npm install webpack-dev-server html-webpack-plugin --save-dev

依赖配置

webpack.config.js 中增加以下配置。

const path = require('path'),
      autoprefix = require('autoprefixer'),
      uglify = require('uglifyjs-webpack-plugin'),
      htmlWebpackPlugin = require('html-webpack-plugin'),
      extractTextPlugin = require('extract-text-webpack-plugin');

plugins 配置

plugins: [
  // new extractTextPlugin("css/styles.css"),
  new uglify(),
  new htmlWebpackPlugin({
    removeComments: true,
    collapseWhitespace: true,
    filename: 'index.html',
    template: path.resolve(__dirname, 'src/index.html'),
    title: 'webpack-demo',
    chunksSortMode: 'manual',
    chunks: ['index'],
    excludeChunks: ['node_modules'],
    hash: true
  })
]

dev-server 配置

devServer: {
  watchOptions: {
    ignored: /node_modules/
  },
  host: 'localhost',
  port: 3000
}

pageage.json 配置NPM服务器启动脚本

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "webpack": "webpack --config webpack.config.js",
  "dev": "webpack-dev-server --host localhost --content-base dist/ --hot --config webpack.config.js --progress --display-modules --colors --display-reasons"
}

inedex.html 模板配置

配置动态渲染标题及移除之前引入的index.js文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
  
</body>
</html>

运行测试

执行脚本 npm run dev,服务器启动后,监听3000端口。
访问http://localhost:3000/连接,出现以下页面,代表服务器配置成功。

w6.png

9. 编译图片资源

编译图片资源需要用到 file-loader、url-loader、image-webpack-loader。

file-loader:可以用来解析图片文件;
url-loader:增强版的file-loader,可以根据需求选择性把小图片编码成base64格式;
image-webpack-loader:用于图片压缩;

依赖安装

npm i file-loader url-loader image-webpack-loader --save-dev

依赖配置

webpack.config.js 中增加以下配置。

module 配置

{
  test: /\.(png|jpg|jpeg|gif|ico)$/i,
  loader: [
    'url-loader?limit=1024&name=img/[name]-[hash:16].[ext]',
    'image-webpack-loader'
  ]
}

index.scss 文件样式修改

#app {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 200px;
  height: 200px;
  margin: 20px auto;
  border: 1px solid transparent;
  box-shadow: 2px 3px 5px #ddd;
  box-sizing: border-box;

  &:hover {
    border-color: #ddd;
  }
  
  .img {
    width: 100px;
  }
}

index.js 引入图片并添加到页面

import '../scss/index.scss'

import icon from '../images/icon.jpg';

const App = (doc) => {
  const app = doc.createElement('div'),
        img = doc.createElement('img');

  app.id = 'app';

  img.src = icon;
  img.className = 'img';

  app.appendChild(img);

  document.body.appendChild(app);
  
}

App(document);

运行测试

运行 npm run dev 命令,启动服务器,出现以下页面效果,说明配置已经完成。

w7.png

10. 总结

通过这篇文章,记录如何用webpack4搭建一个开发环境,用于ES6模块化项目的开发。
当然在实际场景中,还需要复杂的业务,对配置进行进一步的调整与优化。

完整配置如下:

webpack.config.js 配置

const path = require('path'),
      autoprefix = require('autoprefixer'),
      uglify = require('uglifyjs-webpack-plugin'),
      htmlWebpackPlugin = require('html-webpack-plugin'),
      extractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: {
    index: path.resolve(__dirname, 'src/js/index.js')
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: path.resolve(__dirname, 'node_modules'),
        query: {
          'presets': ['latest']
        }
      },
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          'css-loader',
          { 
            loader: 'postcss-loader',
            options: {
              plugins: function () {
                return [autoprefix('last 5 versions')]
              }
            }
          },
          'sass-loader'
        ]
      },
      // {
      //   test: /\.scss$/,
      //   use: extractTextPlugin.extract({
      //     fallback: 'style-loader',
      //     use: [
      //       'css-loader',
      //       { 
      //         loader: 'postcss-loader',
      //         options: {
      //           plugins: function () {
      //             return [autoprefix('last 5 versions')]
      //           }
      //         }
      //       },
      //       'sass-loader'
      //     ]
      //   })
      // },
      {
        test: /\.(png|jpg|jpeg|gif|ico)$/i,
        loader: [
          'url-loader?limit=1024&name=img/[name]-[hash:16].[ext]',
          'image-webpack-loader'
        ]
      }
    ]
  },
  plugins: [
    // new extractTextPlugin("css/styles.css"),
    new uglify(),
    new htmlWebpackPlugin({
      removeComments: true,
      collapseWhitespace: true,
      filename: 'index.html',
      template: path.resolve(__dirname, 'src/index.html'),
      title: 'webpack-demo',
      chunksSortMode: 'manual',
      chunks: ['index'],
      excludeChunks: ['node_modules'],
      hash: true
    })
  ],
  devServer: {
    watchOptions: {
      ignored: /node_modules/
    },
    host: 'localhost',
    port: 3000
  }
}