详解webpack与SPA实践之开发环境搭建


Posted in Javascript onDecember 18, 2017

目前,公司前端项目开发技术栈基本改造为使用gulp进行自动化构建,webpack进行项目模块化依赖管理,Vue+ Vuex + Vue-Router作为项目组件化开发框架,为了更深入的理解、使用当前技术栈并与读者分享、交流,计划推出一系列相关学习与实践文章。本篇为开篇,主要讲述如何使用webpack搭建开发环境。

项目初始化

以你喜欢的任意方式,创建项目根目录,如:

mkdir vue-hello

初始化包模块管理文件

进入项目根目录,初始化项目包模块管理文件package.json:

npm init

命令台会提示你输入一堆信息,如果你想简单一点,可以添加-y参数,跳过输入步骤,生成默认信息:

npm init -y

详解webpack与SPA实践之开发环境搭建

初始化源码目录

在项目根目录下创建源码目录结构,通常源码目录是src或app,个人喜好是使用src:

详解webpack与SPA实践之开发环境搭建

webpack简述及使用

虽然在本篇文章我们不会对webpack做太过详细的介绍,但是依然希望能帮助读者对webpack拥有更清晰的了解,webpack是什么?

webpack is a tool to build JavaScript modules in your application

webpack是一个帮助你的应用构建JavaScript模块的工具。

接下来,我们介绍几个知识点帮助理解webpack:模块化,webpack原理及其与grunt和gulp的比较。

模块化

模块化 指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程。

模块是一个可组合、分解和更换的单元,将一个系统分解成若干模块,单元;大家遵循一定的规范,各司其职,各自开发不同模块;之后可以较低成本的将模块组合起来,构成一个完整的系统,极大方便了团队成员之间的协助开发,产出效率得到有效提升。

WEBPACK原理

webpack是一个帮助你的应用构建JavaScript模块的工具,其本质只能处理JavaScript,那你会疑惑了,不是说使用webpack,可以很方便的在JavaScript代码中引用图片,CSS等资源嘛?是的,这正是webpack的优势,那怎么实现的呢?这就要涉及到webpack中的一个概念:加载器(loader)。

加载器 是作用于应用资源文件的转换器,它们是一系列JavaScript函数,接受资源文件的内容做参数,然后返回新的资源(以一个JavaScript模块的形式返回)。

所以,对于webpack,我们明确三点:

  1. 模块:webpack中一切资源文件(JavaScript, 样式, 图片资源等)皆视为模块;
  2. 加载器:webpack通过加载器(JavaScript函数)将其他资源处理(构建)成JavaScript模块;
  3. 管理依赖:webpack在编译模块时,就能分析查找该模块内的依赖,可以很好的处理不同模块间的依赖;

WEBPACK对比GRUNT/GULP

grunt

打开grunt官网,你可以看到最醒目的介绍:The JavaScript Task Runner,还有一个关键字automation ? 自动化,其定位是一个JavaScript的自动化构建任务处理器,帮助开发者自动化处理项目的构建流程;

gulp

gulp官网的定义是:Automate and enhance your workflow,自动强化项目构建流程,其与grunt的目标一致,都是帮助开发者自动化处理项目的构建流程,不同的是gulp实现方式是基于流的,即以流的方式处理文件,而grunt是以二进制方式处理文件,gulp使用性能是要强于grunt的;

总结

  1. webpack定位是一个模块化管理工具,而grunt/gulp都是自动化任务流程构建工具;
  2. grunt基于二进制处理文件,gulp基于流式处理文件,效率比grunt更高一些;
  3. webpack强大特性,使得其添加诸多插件可以替代grunt/gulp,但是目前的实践项目中,通常webpack结合gulp或grunt使用(各自处理各自专长的任务);

安装

首先安装webpack,npm或yarn都可以,无甚区别:

npm install --save-dev webpack

关于此处的--save和-dev参数做简要说明:

  1. --save是声明将安装依赖添加入package.json文件;
  2. 默认地,使用npm安装包模块依赖时,依赖关系存储在在"dependencies"属性对象内,表示项目依赖;
  3. webpack是作为开发环境依赖的,不是作为源码直接调用,所以添加-dev参数,以声明其是开发环境依赖;

webpack配置介绍

实践项目使用webpack完成自动化构建,本地服务调试与热加载,首先在根目录下创建webpack的配置入口文件webpack.config.js,基本内容结构如下:

var path = require('path');

  module.exports = {
    context: path.resolve(__dirname, 'src/'),
    entry: {
      app: './scripts/app.js'
    },
    output: {
      filename: '[name].js',
      path: path.resolve(__dirname, 'dist/scripts/')
    },
    module: {
      loaders: []
    },
    resolve: {
      modules:[],
      alias: {},
      extensions: []
    },
    plugins: [],
    devServer: {}
  };

如上,webpack配置文件使用module.exports方式导出配置对象,webpack执行时会默认读取项目根目录下webpack.config.js文件,当然可以手动配置指定一个文件作为配置文件,我们不讨论,可以参考webpack文档,接下来对webpack配置内容做简要介绍,如果你对webpack使用比较熟悉,可以跳过此节。

文件处理配置

和webpack文件处理相关的几个配置属性主要有三个:目录上下文信息(context), 处理文件入口信息(entry), 文件输出信息(output)。

目录上下文信息(context)

设置解析处理文件入口的相对目录,值为一个绝对目录路径,默认为当前执行目录,通常即项目根目录,在Node中其值与process.cwd()相同,如:

context: path.resolve(__dirname, 'src/'),

如上即解析为项目根目录下的src目录。

处理文件入口信息(entry)

处理文件入口,值可以是字符串,或数组,或对象,值为字符串或数组时,即输出单文件,值为对象,可以输出多文件,输出文件名称等信息参考文件输出信息(output)。

文件输出信息(output)

此配置声明webpack编译输出文件的文件名等信息,如:

filename: '[name].js',

声明文件名就是模块(chunk)名,对应在entry中定义的入口,你可能还见过[id],[hash],[chunkhash]这些用法,现做简单介绍:

  1. [id]:该值表示webpack编译模块(chunk)的id,通常是一个数字;
  2. [name]:该值表示webpack编译模块(chunk)名,对应entry中定义的入口名或文件名;
  3. [chunkhash]:该值表示webpack编译模块(chunk)hash值,根据模块内容计算出的一个md5值;
  4. [hash]:该值表示webapck编译对象hash值,根据编译对象计算出的md5值;

编译对象,即webpack执行时读取配置后生成的一个编译配置对象,包含模块,待编译文件,相对于上次编译的变更文件等诸多信息,需要注意的是该对象在webpack启动读取配置文件后形成,在此次编译过程保持不变。

  1. output.path:定义输出文件所在目录;
  2. output.publicPath:定义输出文件在浏览器访问时的基础相对路径,可以与后文webpack-dev-server一起使用。

webpack加载器与模块

在介绍加载器配置之前,先看看加载器是什么:

Loaders are transformations that are applied on a resource file of your app. They are functions (running in node.js) that take the source of a resource file as the parameter and return the new source.

加载器是作用于应用资源文件的转换器,它们是一系列函数,接受资源文件的内容做参数,然后返回新的资源(以一个模块的形式返回)。

webpack

webpack解析文件时使用的加载器都声明在module属性的loaders数组中,可以设置一个或多个加载器。

module.noParse:指定某些文件不需要使用解析,支持传入文件路径或正则表达式;

module.loaders:指定解析文件的加载器模块及各模块解析规则,可以设置以下属性:

  1. test: 待解析文件需匹配的规则,通常以文件后缀名称匹配文件;
  2. include:待解析文件所处目录;
  3. exclude: 过滤掉的目录;
  4. loader:加载器模块名称;
  5. loaders多个加载器;

模块解析规则配置

webpack支持在resolve属性对象中配置模块解析规则,主要有root,modules, alias和extensions属性。

resolve.root与resolve.modules

该属性设置的是在开发代码中使用require或import加载某模块时,webpack查找该模块所遵循的查找目录范围,如在源代码中存在:

var utils = require('utils/utils.js');

而root配置如下:

resolve: {
    root: [
      path.resolve('./src/'),
      'node_modules'
    ]
  }

webpack编译时将自动在项目根目录下的src目录内的utils目录下查找utils.js,若存在,则返回,否则进入node_modules目录内查找utils.js模块。

当然,若未设置resolve.root属性值,则webpack默认先从node_modules查找模块,然后在根目录下查找。

注意:resolve.root在webpack v1版本中使用,而在webpack v2 中,使用modules代替,建议使用v2版本,后文也将统一使用modules属性。

resolve.alias

alias,有别名的意思,这里的作用是设置其他模块或路径的别名,webpack在解析模块时,将使用配置值替换该别名,如,在未设置alias属性内容时,即resolve.alias默认是空对象{}时,我们在代码中引用模块:

var Vue = require('vue/dist/vue.js');
var TopHeader = require('components/header.js');

webpack在编译代码时,按resolve.modules声明的顺序依次查找对应模块,如按照上一节定义的resolve.modules:

resolve: {
    root: [
      path.resolve('./src/'),
      'node_modules'
    ]
  }

查找模块时,将首先在src/vue/dist/目录下寻找vue模块,在src/components/目录下查找header.js模块,查找到后返回,否则进入node_modules目录查找。

现在,我们还可以通过resolve.alias为模块或路径声明一个别名,更方便的声明加载模块:

resolve: {
    alias: {
      'vue$': 'vue/dist/vue.js'
      components: path.resolve(__dirname, 'src/components/')
    }
  }

我可以使用如下方式加载模块:

var Vue = require('vue');
var TopHeader = require('components/header.js');

$符号

关于alias的详细使用介绍,请参见文档,本文并不是要介绍webpack文档,在这里介绍一下声明的vue$别名中的$符号:

这里的$符合是正则的文末匹配符,即只有当vue是最后一级路径时,webpack才会将该值解析成别名,进行别名与对应值替换,如vue/test.js中的vue是不会当作别名解析的,而components/header.js中的components则会按照声明的components别名进行解析,其结果是src/components/header.js。

resolve.extensions

该值定义解析模块时的查找文件的后缀顺序,如["", ".js", ".json"],会优先返回js文件,其次json文件,然后是其他文件,注意,这里数组传入了一个空字符串,他的作用是在未找到配置中所有列举的类型文件时,支持webpack返回其他类型文件,但是webpack2.x版本修改了,不支持传入空字符。

webpack插件配置

webpack还支持多种多样的插件拓展,你可以使用它们对你的项目webpack模块构建过程进行额外处理,如代码压缩,图片等资源提交压缩,构建异常捕获和提升,构建流程时间消耗比,等等,而关于这些插件使用的配置在plugins属性数组内,将在后续进行介绍。

项目本地调试与开发

为了方便开发和调试,通常都需要在本地主机开启服务,提供局域网内多终端访问,并且在文件变更时,实时刷新页面,正如grunt和gulp中Browsersync插件提供的功能一样,webpack可以使用webpack-dev-server模块实现。

webpack-dev-server是一个Node.js的express服务器,以webpack开发中间件的形式为webpack包提供服务,当监听到源码文件变更时,会自动重新打包,并且支持配置自动刷新浏览器,重新加载资源。

安装

由于该插件只用于开发模式,所以通过以下npm指令安装:

npm install webpack-dev-server --save-dev

配置

启用webpack-dev-server时,其默认开启8080端口,访问localhost:8080返回当前目录下的index.html,即执行指令当前目录下的静态资源,当然可以通过指令传递参数或在配置文件进行配置指定静态资源目录。

另外需要注意的是,开启webpack-dev-server后,变更文件重新打包后,并不会实际输出到配置的output目录,而是在publicPath属性声明的相对路径所在的内存中读取。

静态资源目录配置(CONTENT BASE)

webpack-dev-server --content-base src/

执行以上指令开启服务后,webpack-dev-server将默认在src/目录返回静态资源,当然也可以在webpack.config.js配置文件中指定:

devServer: {
    contentBase: './src'
  }

访问http://localhost:8080和http://localhost:8080/index.html效果一样,均返回src目录下的index.html文件。

PUBLICPATH与输出文件访问

在前文webpack配置一节中提到output.publicPath属性值表示,在浏览器访问webpack output输出的文件时的基础相对路径,如设置:

output: {
    path: 'dist/scripts',
    filename: 'app.js',
    publicPath: 'assets/'
  }

则在html文件中引用该app.js文件的方式如下:

<script src="assets/app.js"></script>

在浏览器中访问app.js的方式为:http://localhost:8080/assets/app.js

自动刷新(AUTOMATIC REFRESH)

前面说到webpack-dev-server支持文件变更时,自动刷新浏览器,这也是开发者急需的功能,webpack-dev-server支持两种方式实现:

  1. iframe模式(iframe mode):页面通过iframe窗口插入然后变更时自动重新加载;
  2. inline模式(inline mode):通过sock.js(比如websocket协议,轮询等方式)在页面嵌入一个小型客户端与webpack-dev-server服务器建立连接,当发生变更重新打包时,通过此连接通知页面重新加载;

iframe模式

使用默认的iframe模式时,不需要进行任何配置,可以直接访问:http://localhost:8080/webpack-dev-server/index.html即可,如图,通过iframe插入我们的页面:

详解webpack与SPA实践之开发环境搭建

注意:

  1. 在应用页面上方会有状态栏显示当前应用信息;
  2. 应用URL的变更发生在iframe内部,不会反映在浏览器地址栏;

inline模式

要开启inline模式,只需要指定--inline命令行参数或在配置文件中指定inline: true:

devServer: {
    contentBase: './src',
    inline: true
  }

在此模式下,直接访问http://localhost:8080/index.html或http://localhost:8080即可,此模式与iframe模式的区别在于:

  1. 访问URL不同;
  2. 需指定inline配置参数;
  3. 应用信息在控制台输出;
  4. 应用URL的变更直接反映在浏览器地址栏;

其实inline模式还可以配合Node.Js服务运行,之后再介绍,这里只说明了其在HTML文档中的使用。

热替换(HOT MODULE REPLACEMENT)

除了自动刷新,webpack-dev-server的另一大卖点是模块热替换,那么热替换到底是什么?

热替换,即文件发生变更时,webpack包只替换发生变更的模块,而不需要全部替换;浏览器不需要完全重新加载,而可以直接将变更的模块注入到运行中的应用。

开启热替换功能需要指定--hot指令参数,或在配置文件中添加:

devServer: {
    contentBase: './src',
    inline: true,
    hot: true
  }

注意到以上代码,热替换需要与inline模式一起使用,另外需要指定output.publicPath值。

HotModuleReplacementPlugin

配置好后还需要使用webpack.HotModuleReplacementPlugin插件,才能真正启用热替换功能:

plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]

同样的,热替换可以搭配Node.js服务一起使用,之后介绍。

如图,为每个文件(app.js, test.js)都实现了HMR热替换,当发生变更时,只更新变更模块,而不是重新加载所有文件:

详解webpack与SPA实践之开发环境搭建

拥抱ES6

ES6推出以来,广受Jser们青睐,其确实有很多新特性和新规范,值得我们深入学习并使用,未来所有的JavaScript应用都应该拥抱ES6,不过,目前各大浏览器都在推进ES6的实现,在兼容实现之前,我们还需要过渡性的将其转换成ES5语法,最通用的方式就是使用babel转换。

使用BABEL加载器

首先,为了能使用webpack和babel转换js代码,需要使用babel-loader加载器,另外还需要安装babel转换js的转换规则插件,如babel-preset-es2015定义了转换规则,安装方式如下:

npm install --save-dev babel-loader babel-preset-es2015

然后在根目录下创建.babelrc文件,内容:

{
    "presets": ["es2015"]
  }

在webpack.config.js配置文件中,添加相关loader配置:

module: {
    loaders: [
      {
        test: /\.(js|vue)$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  }

其中,test匹配需要转换的文件,exclude匹配不需要转换的文件或目录。

BABEL-POLYFILL

我们需要明白的一点是babel-preset-es2015能做的,只是转换ES6代码成ES5,使得浏览器可以解析执行,但是对于ES6新提出的API,如Promise,Generator等无法简单的转换成ES5代码,这时就需要babel-polyfill了,babel-polyfill是一个垫片,它可以模拟提供所有的ES6功能和特性,可以看作是提供了一个模拟的全局ES6环境。

安装

安装依然很简单,由于是垫片,是需要在源码中使用的,所有指定--save参数:

npm install --save babel-polyfill

使用

不同于babel-preset-es2015,babel-polyfill需要直接打包进源码,而且需要在使用ES6代码前引入一次:

import 'babel-polyfill'

只需要引入一次,因为该垫片提供的是一个模拟的全局ES6环境,而不是模块内部的。

或者直接在webpack.config.js中配置打包入口文件时加入该垫片:

entry: {
    app: ['babel-polyfill', './scripts/app.js']
  },

现在你可以在你的代码中使用任何你想用的ES6 API了。

到此,基本介绍了如何使用webpack搭建开发环境,下一篇将开始介绍如何使用webpack处理CSS及如何使用Vue进行组件化开发。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js宝典学习笔记(上)
Jan 10 Javascript
javascript 数组操作详解
Jan 29 Javascript
javascript事件冒泡和事件捕获详解
May 26 Javascript
js实现Select列表内容自动滚动效果代码
Aug 20 Javascript
原生js模拟淘宝购物车项目实战
Nov 18 Javascript
js css实现垂直方向自适应的三角提示菜单
Jun 26 Javascript
jquery插件treegrid树状表格的使用方法详解(.Net平台)
Jan 03 Javascript
基于vue实现swipe分页组件实例
May 25 Javascript
javascript显示动态时间的方法汇总
Jul 06 Javascript
解决Vue2.0中使用less给元素添加背景图片出现的问题
Sep 03 Javascript
angularjs下ng-repeat点击元素改变样式的实现方法
Sep 12 Javascript
实现一个Vue自定义指令懒加载的方法示例
Jun 04 Javascript
javaScript中的空值和假值
Dec 18 #Javascript
浅谈Webpack自动化构建实践指南
Dec 18 #Javascript
微信小程序实现图片上传、删除和预览功能的方法
Dec 18 #Javascript
mockjs,json-server一起搭建前端通用的数据模拟框架教程
Dec 18 #Javascript
在knockoutjs 上自己实现的flux(实例讲解)
Dec 18 #Javascript
vue的无缝滚动组件vue-seamless-scroll实例
Dec 18 #Javascript
分析JavaScript数组操作难点
Dec 18 #Javascript
You might like
php中如何使对象可以像数组一样进行foreach循环
2013/08/09 PHP
Codeigniter上传图片出现“You did not select a file to upload”错误解决办法
2014/06/12 PHP
PHP实现算式验证码和汉字验证码实例
2015/03/09 PHP
下拉列表选择项的选中在不同浏览器中的兼容性问题探讨
2013/09/18 Javascript
文本框倒叙输入让输入框的焦点始终在最开始的位置
2014/09/01 Javascript
AngularJS+Node.js实现在线聊天室
2015/08/28 Javascript
不得不分享的JavaScript常用方法函数集(下)
2015/12/25 Javascript
javascript回到顶部特效
2016/07/30 Javascript
纯原生js实现table表格的增删
2017/01/05 Javascript
Vue列表页渲染优化详解
2017/07/24 Javascript
jQuery实现的粘性滚动导航栏效果实例【附源码下载】
2017/10/19 jQuery
React 路由懒加载的几种实现方案
2018/10/23 Javascript
微信小程序实现的一键拨号功能示例
2019/04/24 Javascript
浅谈vuex的基本用法和mapaction传值问题
2019/11/08 Javascript
在vue中使用vant TreeSelect分类选择组件操作
2020/11/02 Javascript
python将html转成PDF的实现代码(包含中文)
2013/03/04 Python
django开发教程之利用缓存文件进行页面缓存的方法
2017/11/10 Python
pandas按若干个列的组合条件筛选数据的方法
2018/04/11 Python
django+echart数据动态显示的例子
2019/08/12 Python
Python使用scipy模块实现一维卷积运算示例
2019/09/05 Python
Python中flatten( ),matrix.A用法说明
2020/07/05 Python
Python SMTP发送电子邮件的示例
2020/09/23 Python
python接口自动化框架实战
2020/12/23 Python
Django url 路由匹配过程详解
2021/01/22 Python
详解html5 postMessage解决跨域通信的问题
2018/08/17 HTML / CSS
幼儿园小班植树节活动方案
2014/03/04 职场文书
文案策划求职信
2014/03/18 职场文书
担保书格式及范文
2014/04/01 职场文书
党员干部反四风对照检查材料思想汇报
2014/09/14 职场文书
群众路线表态发言材料
2014/10/17 职场文书
消防安全月活动总结
2015/05/08 职场文书
建国大业观后感
2015/06/01 职场文书
写作技巧:优秀文案必备的3种结构
2019/08/19 职场文书
解决golang在import自己的包报错的问题
2021/04/29 Golang
进阶篇之linux环境下安装MySQL数据库
2022/04/09 MySQL
解决 redis 无法远程连接
2022/05/15 Redis